Serveur Web d'apprentissage automatique "VKF-solveur"

Désormais, aux yeux du grand public, l'apprentissage automatique est fortement associé à diverses options de formation des réseaux de neurones. Si au départ il s'agissait de réseaux entièrement connectés, ils ont ensuite été remplacés par des réseaux convolutifs et récurrents, maintenant ils sont devenus des options complètement exotiques comme les réseaux GAN et LTSM. Outre les volumes croissants d'échantillons nécessaires à leur formation, ils souffrent toujours de l'impossibilité d'expliquer pourquoi telle ou telle décision a été prise. Mais il existe également des approches structurelles de l'apprentissage automatique, dont l'implémentation logicielle de l'une d'entre elles est décrite dans cet article.







Il s'agit d'une approche domestique de l'apprentissage automatique, appelée la méthode VKF d'apprentissage automatique basée sur la théorie du réseau. L'histoire d'origine et le choix du nom sont expliqués à la toute fin de cet article.



1. Description de la méthode



Initialement, le système entier a été créé par l'auteur en C ++ en tant qu'application console, puis il a été connecté à une base de données sous le contrôle du SGBD MariaDB (en utilisant la bibliothèque mariadb ++), puis transformé en une bibliothèque Python (en utilisant le package pybind11).

Plusieurs tableaux pour tester les algorithmes d'apprentissage automatique du référentiel de l'Université de Californie à Irvine ont été sélectionnés comme données de test.



Sur le tableau Mushrooms, contenant des descriptions de 8124 champignons nord-américains, le système a montré des résultats à 100%. Plus précisément, à l'aide d'un générateur de nombres aléatoires, les données initiales ont été divisées en un échantillon d'apprentissage (2088 comestibles et 1944 champignons vénéneux) et un échantillon test (2120 comestibles et 1972 vénéneux). Après avoir calculé environ 100 hypothèses sur les raisons de la comestibilité, tous les cas de test ont été prédits correctement. Puisque l'algorithme utilise une chaîne de Markov appariée, un nombre suffisant d'hypothèses peut varier. Très souvent, il s'est avéré suffisant pour générer 50 hypothèses aléatoires. Notez que lors de la génération des causes de toxicité, le nombre d'hypothèses requises est groupé autour de 120, cependant, tous les cas de test sont correctement prédits dans ce cas. Kaggle.com a un concours de classification des champignonsoù de nombreux auteurs ont atteint une précision de 100%. Mais la plupart des solutions sont des réseaux de neurones. Notre approche permet au cueilleur de champignons d'apprendre seulement environ 50 règles. Étant donné que la plupart des signes ne sont pas pertinents, chaque hypothèse sera également une conjonction d'un petit nombre de valeurs de signes essentiels, ce qui les rend faciles à retenir. Après cela, le cueilleur de champignons peut opter pour des champignons sans craindre de prendre un champignon ou de manquer un champignon comestible.



Voici un exemple d'une des hypothèses sur la base desquelles on peut supposer que le champignon est comestible:

[('gill_attachment', 'free'), ('gill_spacing', 'close'), ('gill_size', 'wide'), ('stalk_shape ',' agrandissement '), (' stalk_surface_below_ring ',' scaly '), (' veil_type ',' partial '), (' veil_color ',' white '), ('ring_number ',' one '), (' ring_type ',' pendant ')]



J'attire votre attention sur le fait que seuls 9 signes sur 22 figurent dans la liste, étant donné que les 13 signes de similitude restants ne sont pas observés dans les champignons comestibles qui ont donné lieu à cette raison.



Un autre tableau était SPECT Hearts. Là, la précision de prédiction des cas de test a atteint 86,1%, ce qui s'est avéré légèrement supérieur aux résultats (84%) du système d'apprentissage automatique CLIP3, basé sur la couverture des exemples de formation utilisant la programmation en nombres entiers, utilisée par les auteurs du tableau. Je pense qu'en raison de la structure de la description des tomogrammes du cœur, qui y sont déjà pré-codés avec des signes binaires, il n'est pas possible d'améliorer considérablement la qualité des prévisions.



L'auteur a tout récemment proposé (et implémenté en logiciel) une extension de son approche du traitement des données décrite par des caractéristiques continues (numériques). À certains égards, son approche est similaire au système C4.5 d'arbres de décision de formation. Cette variante a été testée sur le tableau Wine Quality. Ce tableau décrit la qualité des vins portugais. Les résultats sont encourageants: si l'on prend des vins rouges de grande qualité, les hypothèses expliquent pleinement leurs notes élevées.



2. Choix de la plateforme



Actuellement, grâce aux efforts des étudiants du Département des systèmes intelligents de l'Université humanitaire d'État russe, une série de serveurs Web est en cours de création pour différents types de tâches (en utilisant le bundle Nginx + Gunicorn + Django).



Cependant, j'ai décidé de décrire ma version personnelle ici (en utilisant les bundles aiohttp, aiojobs et aiomysql). Le module aiomcache n'est pas utilisé en raison de problèmes de sécurité connus.



L'option proposée présente plusieurs avantages:



  1. il est asynchrone en raison de l'utilisation de aiohttp;
  2. il permet la création de modèles Jinja2;
  3. il fonctionne avec un pool de connexions à la base de données via aiomysql;
  4. Il permet le lancement de processus informatiques indépendants via aiojobs.aiohttp.spawn.


Signalons également les inconvénients évidents (par rapport à Django):



  1. pas de mappage relationnel d'objets (ORM);
  2. il est plus difficile d'organiser l'utilisation du serveur proxy Nginx;
  3. pas de Django Template Language (DTL).


Chacune des deux options vise différentes stratégies pour travailler avec un serveur Web. La stratégie synchrone (sur Django) vise le mode mono-utilisateur, dans lequel l'expert travaille avec une seule base de données à tout moment. Bien que les procédures probabilistes de la méthode VKF soient remarquablement parallèles, il existe néanmoins une possibilité théorique que les procédures d'apprentissage automatique prennent un temps considérable. Par conséquent, l'option discutée dans cette note s'adresse à plusieurs experts, chacun pouvant travailler simultanément (dans différents onglets du navigateur) avec différentes bases de données qui diffèrent non seulement en données mais également en termes de représentation (différents réseaux sur les valeurs d'attributs discrets, différentes régressions significatives et nombre seuils pour continu). Ensuite, lors du démarrage de l'expérience CCF dans un onglet, l'expert peut passer à un autre,où préparera ou analysera une expérience avec différentes données et / ou paramètres.



Pour prendre en compte plusieurs utilisateurs, expériences et différentes étapes de leur localisation, il existe une base de données de service (vkf) avec deux tables (utilisateurs, expériences). Si la table user stocke le login et le mot de passe de tous les utilisateurs enregistrés, les expériences, en plus des noms des tables auxiliaires et principales de chaque expérience, conservent l'état de la plénitude de ces tables. Nous avons abandonné aiohttp_session, car vous devez toujours utiliser le serveur proxy Nginx pour protéger les données critiques.



Voici la structure du tableau des expériences:



  • id int (11) CLÉ PRIMAIRE NON NULL
  • expName varchar (255) NOT NULL
  • encodeur varchar (255)
  • goodEncoder tinyint (1)
  • treillis varchar (255)
  • goodLattices tinyint (1)
  • varchar complexe (255)
  • goodComplex tinyint (1)
  • bords varchar (255)
  • goodVerges tinyint (1)
  • vergesTotal int (11)
  • trains varchar (255) NOT NULL
  • goodTrains tinyint (1)
  • tests varchar(255)
  • goodTests tinyint(1)
  • hypotheses varchar(255) NOT NULL
  • goodHypotheses tinyint(1)
  • type varchar(255) NOT NULL


Il convient de noter qu'il existe certaines séquences de préparation de données pour les expériences CCF, qui, malheureusement, sont radicalement différentes pour les cas discrets et continus. Le cas des attributs mixtes combine les exigences des deux types.



discret: => goodLattices (semi-automatique)

discrete: goodLattices => goodEncoder (automatic)

discrete: goodEncoder => goodTrains (semi-automatic)

discrete: goodEncoder, goodTrains => goodHypotheses (automatique)

discrete: goodEncoder => goodTests (semi-automatique),

discret goodEncoder, goodHypotheses => (automatique)

continu: => goodVerges (manuel)

continu: goodVerges => goodTrains (manuel)

continu: goodTrains => goodComplex (automatique)

continu: goodComplex, goodTrains => goodHypotheses (automatique)

continu: goodVerges => goodTests (manuel)

continu: goodTests, goodComplex, goodHypotheses => (automatique)



La bibliothèque d'apprentissage automatique elle-même s'appelle vkf.cpython -36m-x86_64-linux-gnu.so pour Linux ou vkf.cp36-win32.pyd pour Windows. (36 est la version de Python pour laquelle cette bibliothèque a été conçue).



Le terme «automatique» désigne le travail de cette bibliothèque, «semi-automatique» signifie le travail de la bibliothèque auxiliaire vkfencoder.cpython-36m-x86_64-linux-gnu.so. Enfin, le mode «manuel» est un appel à des programmes qui traitent spécifiquement les données d'une expérience particulière et sont maintenant transférés vers la bibliothèque vkfencoder.



3. Détails de mise en œuvre



Lors de la création d'un serveur Web, nous utilisons l'approche "Vue / Modèle / Contrôle"



. Le code Python se trouve dans 5 fichiers:



  1. app.py - fichier de lancement d'application
  2. control.py - fichier contenant les procédures d'utilisation du solveur VKF
  3. models.py - fichier avec des classes pour traiter les données et travailler avec la base de données
  4. settings.py - fichier avec les paramètres de l'application
  5. views.py - fichier avec visualisation et gestion des routes (routes).


Le fichier app.py ressemble à ceci:



#! /usr/bin/env python
import asyncio
import jinja2
import aiohttp_jinja2

from settings import SITE_HOST as siteHost
from settings import SITE_PORT as sitePort

from aiohttp import web
from aiojobs.aiohttp import setup

from views import routes

async def init(loop):
    app = web.Application(loop=loop)
    # install aiojobs.aiohttp
    setup(app)
    # install jinja2 templates
    aiohttp_jinja2.setup(app, 
        loader=jinja2.FileSystemLoader('./template'))
    # add routes from api/views.py
    app.router.add_routes(routes)
    return app

loop = asyncio.get_event_loop()
try:
    app = loop.run_until_complete(init(loop))
    web.run_app(app, host=siteHost, port=sitePort)
except:
    loop.stop()


Je ne pense pas que quelque chose doive être expliqué ici. Le fichier suivant dans l'ordre d'inclusion dans le projet est views.py:



import aiohttp_jinja2
from aiohttp import web#, WSMsgType
from aiojobs.aiohttp import spawn#, get_scheduler
from models import User
from models import Expert
from models import Experiment
from models import Solver
from models import Predictor

routes = web.RouteTableDef()

@routes.view(r'/tests/{name}', name='test-name')
class Predict(web.View):
    @aiohttp_jinja2.template('tests.html')
    async def get(self):
        return {'explanation': 'Please, confirm prediction!'}

    async def post(self):
        data = await self.request.post()
        db_name = self.request.match_info['name']
        analogy = Predictor(db_name, data)
        await analogy.load_data()
        job = await spawn(self.request, analogy.make_prediction())
        return await job.wait()

@routes.view(r'/vkf/{name}', name='vkf-name')
class Generate(web.View):
    #@aiohttp_jinja2.template('vkf.html')
    async def get(self):
        db_name = self.request.match_info['name']
        solver = Solver(db_name)
        await solver.load_data()
        context = { 'dbname': str(solver.dbname),
                    'encoder': str(solver.encoder),
                    'lattices': str(solver.lattices),
                    'good_lattices': bool(solver.lattices),
                    'verges': str(solver.verges),
                    'good_verges': bool(solver.good_verges),
                    'complex': str(solver.complex),
                    'good_complex': bool(solver.good_complex),
                    'trains': str(solver.trains),
                    'good_trains': bool(solver.good_trains),
                    'hypotheses': str(solver.hypotheses),
                    'type': str(solver.type)
            }
        response = aiohttp_jinja2.render_template('vkf.html', 
            self.request, context)
        return response
            
    async def post(self):
        data = await self.request.post()
        step = data.get('value')
        db_name = self.request.match_info['name']
        if step is 'init':
            location = self.request.app.router['experiment-name'].url_for(
                name=db_name)
            raise web.HTTPFound(location=location)
        solver = Solver(db_name)
        await solver.load_data()
        if step is 'populate':
            job = await spawn(self.request, solver.create_tables())
            return await job.wait()                
        if step is 'compute':
            job = await spawn(self.request, solver.compute_tables())
            return await job.wait()                
        if step is 'generate':
            hypotheses_total = data.get('hypotheses_total')
            threads_total = data.get('threads_total')
            job = await spawn(self.request, solver.make_induction(
                hypotheses_total, threads_total))
            return await job.wait()                

@routes.view(r'/experiment/{name}', name='experiment-name')
class Prepare(web.View):
    @aiohttp_jinja2.template('expert.html')
    async def get(self):
        return {'explanation': 'Please, enter your data'}

    async def post(self):
        data = await self.request.post()
        db_name = self.request.match_info['name']
        experiment = Experiment(db_name, data)
        job = await spawn(self.request, experiment.create_experiment())
        return await job.wait()


J'ai raccourci ce fichier pour le bien de cette note, en laissant de côté les classes qui servent les routes utilitaires:



  1. Auth '/' . , SignIn, '/signin'. , '/user/{name}'.
  2. SignIn '/signin' .
  3. Select '/user/{name}' , . '/vkf/{name}' '/experiment/{name}' ( ).


Les classes restantes gèrent les itinéraires responsables des étapes d'apprentissage automatique:



  1. la classe Prepare traite les routes '/ experiment / {nom}' et collecte les noms des tables de service et les paramètres numériques requis pour exécuter les procédures de la méthode VKF. Après avoir enregistré ces informations dans la base de données, l'utilisateur est redirigé vers la route '/ vkf / {name}'.
  2. la classe Generate traite les routes '/ vkf / {nom}' et démarre les différentes étapes de la procédure d'induction de la méthode VKF, en fonction de l'état de préparation des données par l'expert.
  3. la classe Predict traite les routes '/ tests / {nom}' et démarre la procédure de la méthode de prédiction VKF par analogie.


Pour passer un grand nombre de paramètres au formulaire vkf.html, une construction de aiohttp_jinja2 est utilisée



response = aiohttp_jinja2.render_template('vkf.html', self.request, context)
return response




Notez également l'utilisation de l'appel spawn du package aiojobs.aiohttp:



job = await spawn(self.request, 
    solver.make_induction(hypotheses_total, threads_total))
return await job.wait()


Ceci est nécessaire pour appeler en toute sécurité des coroutines à partir des classes définies dans le fichier models.py qui traitent les données d'utilisateur et d'expérimentation stockées dans une base de données gérée par MariaDB:



import aiomysql
from aiohttp import web

from settings import AUX_NAME as auxName
from settings import AUTH_TABLE as authTable
from settings import AUX_TABLE as auxTable
from settings import SECRET_KEY as secretKey
from settings import DB_HOST as dbHost

from control import createAuxTables
from control import createMainTables
from control import computeAuxTables
from control import induction
from control import prediction

class Experiment():
    def __init__(self, dbName, data, **kw):
        self.encoder = data.get('encoder_table')
        self.lattices = data.get('lattices_table')
        self.complex = data.get('complex_table')
        self.verges = data.get('verges_table')
        self.verges_total = data.get('verges_total')
        self.trains = data.get('training_table')
        self.tests = data.get('tests_table')
        self.hypotheses = data.get('hypotheses_table')
        self.type = data.get('type')
        self.auxname = auxName
        self.auxtable = auxTable
        self.dbhost = dbHost
        self.secret = secretKey
        self.dbname = dbName

    async def create_db(self, pool):
        async with pool.acquire() as conn:
            async with conn.cursor() as cur:
                await cur.execute("CREATE DATABASE IF NOT EXISTS " +
                    str(self.dbname)) 
                await conn.commit() 
        await createAuxTables(self)
 
    async def register_experiment(self, pool):
        async with pool.acquire() as conn:
            async with conn.cursor() as cur:
                sql = "INSERT INTO " + str(self.auxname) + "." + 
                    str(self.auxtable)
                sql += " VALUES(NULL, '" 
                sql += str(self.dbname) 
                sql += "', '" 
                sql += str(self.encoder) 
                sql += "', 0, '" #goodEncoder
                sql += str(self.lattices) 
                sql += "', 0, '" #goodLattices
                sql += str(self.complex) 
                sql += "', 0, '" #goodComplex 
                sql += str(self.verges_total) 
                sql += "', 0, " #goodVerges
                sql += str(self.verges_total) 
                sql += ", '" 
                sql += str(self.trains) 
                sql += "', 0, '" #goodTrains 
                sql += str(self.tests) 
                sql += "', 0, '" #goodTests 
                sql += str(self.hypotheses) 
                sql += "', 0, '" #goodHypotheses 
                sql += str(self.type)
                sql += "')"
                await cur.execute(sql)
                await conn.commit() 

    async def create_experiment(self, **kw):
        pool = await aiomysql.create_pool(host=self.dbhost, 
            user='root', password=self.secret)
        task1 = self.create_db(pool=pool)
        task2 = self.register_experiment(pool=pool)
        tasks = [asyncio.ensure_future(task1), 
            asyncio.ensure_future(task2)]
        await asyncio.gather(*tasks)
        pool.close()
        await pool.wait_closed()
        raise web.HTTPFound(location='/vkf/' + self.dbname)        

class Solver():
    def __init__(self, dbName, **kw):
        self.auxname = auxName
        self.auxtable = auxTable
        self.dbhost = dbHost
        self.dbname = dbName
        self.secret = secretKey

    async def load_data(self, **kw):    
        pool = await aiomysql.create_pool(host=dbHost, 
            user='root', password=secretKey, db=auxName)
        async with pool.acquire() as conn:
            async with conn.cursor() as cur:
                sql = "SELECT * FROM "
                sql += str(auxTable)
                sql += " WHERE  expName='"
                sql += str(self.dbname)
                sql += "'"
                await cur.execute(sql)
                row = cur.fetchone()
                await cur.close()
        pool.close()
        await pool.wait_closed()
        self.encoder = str(row.result()[2])
        self.good_encoder = bool(row.result()[3])
        self.lattices = str(row.result()[4])
        self.good_lattices = bool(row.result()[5])
        self.complex = str(row.result()[6])
        self.good_complex = bool(row.result()[7])
        self.verges = str(row.result()[8])
        self.good_verges = bool(row.result()[9])
        self.verges_total = int(row.result()[10])
        self.trains = str(row.result()[11])
        self.good_trains = bool(row.result()[12])
        self.hypotheses = str(row.result()[15])
        self.good_hypotheses = bool(row.result()[16])
        self.type = str(row.result()[17])

    async def create_tables(self, **kw):
        await createMainTables(self)
        pool = await aiomysql.create_pool(host=self.dbhost, user='root', 
            password=self.secret, db=self.auxname)
        async with pool.acquire() as conn:
            async with conn.cursor() as cur:
                sql = "UPDATE "
                sql += str(self.auxtable)
                sql += " SET encoderStatus=1 WHERE dbname='"
                sql += str(self.dbname)
                sql += "'"
                await cur.execute(sql) 
                await conn.commit() 
                await cur.close()
        pool.close()
        await pool.wait_closed()
        raise web.HTTPFound(location='/vkf/' + self.dbname)        

    async def compute_tables(self, **kw):
        await computeAuxTables(self)
        pool = await aiomysql.create_pool(host=self.dbhost, user='root', 
            password=self.secret, db=self.auxname)
        async with pool.acquire() as conn:
            async with conn.cursor() as cur:
                sql = "UPDATE "
                sql += str(self.auxtable)
                sql += " SET complexStatus=1 WHERE dbname='"
                sql += str(self.dbname)
                sql += "'"
                await cur.execute(sql) 
                await conn.commit() 
                await cur.close()
        pool.close()
        await pool.wait_closed()
        raise web.HTTPFound(location='/vkf/' + self.dbname)        

    async def make_induction(self, hypotheses_total, threads_total, **kw):
        await induction(self, hypotheses_total, threads_total)
        pool = await aiomysql.create_pool(host=self.dbhost, user='root', 
            password=self.secret, db=self.auxname)
        async with pool.acquire() as conn:
            async with conn.cursor() as cur:
                sql = "UPDATE "
                sql += str(self.auxtable)
                sql += " SET hypothesesStatus=1 WHERE dbname='"
                sql += str(self.dbname)
                sql += "'"
                await cur.execute(sql) 
                await conn.commit() 
                await cur.close()
        pool.close()
        await pool.wait_closed()
        raise web.HTTPFound(location='/tests/' + self.dbname)        

class Predictor():
    def __init__(self, dbName, data, **kw):
        self.auxname = auxName
        self.auxtable = auxTable
        self.dbhost = dbHost
        self.dbname = dbName
        self.secret = secretKey
        self.plus = 0
        self.minus = 0

    async def load_data(self, **kw):    
        pool = await aiomysql.create_pool(host=dbHost, user='root', 
            password=secretKey, db=auxName)
        async with pool.acquire() as conn:
            async with conn.cursor() as cur:
                sql = "SELECT * FROM "
                sql += str(auxTable)
                sql += " WHERE dbname='"
                sql += str(self.dbname)
                sql += "'"
                await cur.execute(sql) 
                row = cur.fetchone()
                await cur.close()
        pool.close()
        await pool.wait_closed()
        self.encoder = str(row.result()[2])
        self.good_encoder = bool(row.result()[3])
        self.complex = str(row.result()[6])
        self.good_complex = bool(row.result()[7])
        self.verges = str(row.result()[8])
        self.trains = str(row.result()[11])
        self.tests = str(row.result()[13])
        self.good_tests = bool(row.result()[14])
        self.hypotheses = str(row.result()[15])
        self.good_hypotheses = bool(row.result()[16])
        self.type = str(row.result()[17])

    async def make_prediction(self, **kw):
        if self.good_tests and self.good_hypotheses:
            await induction(self, 0, 1)
            await prediction(self)
            message_body = str(self.plus)
            message_body += " correct positive cases. "
            message_body += str(self.minus)
            message_body += " correct negative cases."
            raise web.HTTPException(body=message_body)
        else:
            raise web.HTTPFound(location='/vkf/' + self.dbname)




Encore une fois, certaines classes d'assistance sont masquées:



  1. La classe User correspond au visiteur du site. Il vous permet de vous inscrire et de vous connecter en tant qu'expert.
  2. La classe Expert vous permet de choisir l'une des expériences.


Les classes restantes correspondent aux procédures principales:



  1. La classe Experiment vous permet de spécifier les noms des tables clés et auxiliaires et les paramètres nécessaires pour mener des expériences VKF.
  2. La classe Solver est responsable de la généralisation inductive dans la méthode ICF.
  3. La classe Predictor est responsable des prédictions par analogie dans la méthode VKF.


Il est important de noter l'utilisation de la construction create_pool () du package aiomysql. Il vous permet de travailler avec la base de données dans plusieurs connexions. Les routines assure_future () et collecte () du module asyncio doivent également attendre la fin de l'exécution.



pool = await aiomysql.create_pool(host=self.dbhost, 
    user='root', password=self.secret)
task1 = self.create_db(pool=pool)
task2 = self.register_experiment(pool=pool)
tasks = [asyncio.ensure_future(task1), 
    asyncio.ensure_future(task2)]
await asyncio.gather(*tasks)
pool.close()
await pool.wait_closed()


Lors de la lecture d'une table, row = cur.fetchone () renvoie un futur, donc row.result () renvoie un enregistrement de base de données à partir de laquelle les valeurs de champ peuvent être récupérées (par exemple, str (row.result () [2]) récupère le nom de la table avec codant les valeurs des entités discrètes).




pool = await aiomysql.create_pool(host=dbHost, user='root', 
    password=secretKey, db=auxName)
async with pool.acquire() as conn:
    async with conn.cursor() as cur:
        await cur.execute(sql) 
        row = cur.fetchone()
        await cur.close()
pool.close()
await pool.wait_closed()
self.encoder = str(row.result()[2])


Les paramètres système clés sont importés depuis le fichier .env ou (s'il n'y en a pas) depuis le fichier settings.py.



from os.path import isfile
from envparse import env

if isfile('.env'):
    env.read_envfile('.env')

AUX_NAME = env.str('AUX_NAME', default='vkf')
AUTH_TABLE = env.str('AUTH_TABLE', default='users')
AUX_TABLE = env.str('AUX_TABLE', default='experiments')
DB_HOST = env.str('DB_HOST', default='127.0.0.1')
DB_HOST = env.str('DB_PORT', default=3306)
DEBUG = env.bool('DEBUG', default=False)
SECRET_KEY = env.str('SECRET_KEY', default='toor')
SITE_HOST = env.str('HOST', default='127.0.0.1')
SITE_PORT = env.int('PORT', default=8080)


Il est important de noter que localhost doit être spécifié par adresse IP, sinon aiomysql essaiera de se connecter à la base de données via un socket Unix, qui peut ne pas fonctionner sous Windows. Enfin, lisons le dernier fichier (control.py):



import os
import asyncio
import vkf

async def createAuxTables(db_data):
    if  db_data.type is not "discrete":
        await vkf.CAttributes(db_data.verges, db_data.dbname, 
            '127.0.0.1', 'root', db_data.secret)
    if  db_data.type is not "continuous":
        await vkf.DAttributes(db_data.encoder, db_data.dbname, 
            '127.0.0.1', 'root', db_data.secret)
        await vkf.Lattices(db_data.lattices, db_data.dbname, 
            '127.0.0.1', 'root', db_data.secret) 

async def createMainTables(db_data):
    if  db_data.type is "continuous":
        await vkf.CData(db_data.trains, db_data.verges, 
            db_data.dbname, '127.0.0.1', 'root', db_data.secret)
        await vkf.CData(db_data.tests, db_data.verges, 
            db_data.dbname, '127.0.0.1', 'root', db_data.secret)
    if  db_data.type is "discrete":
        await vkf.FCA(db_data.lattices, db_data.encoder, 
            db_data.dbname, '127.0.0.1', 'root', db_data.secret)
        await vkf.DData(db_data.trains, db_data.encoder, 
            db_data.dbname, '127.0.0.1', 'root', db_data.secret)
        await vkf.DData(db_data.tests, db_data.encoder, 
            db_data.dbname, '127.0.0.1', 'root', db_data.secret)
    if  db_data.type is "full":
        await vkf.FCA(db_data.lattices, db_data.encoder, 
            db_data.dbname, '127.0.0.1', 'root', db_data.secret)
        await vkf.FData(db_data.trains, db_data.encoder, db_data.verges, 
            db_data.dbname, '127.0.0.1', 'root', db_data.secret)
        await vkf.FData(db_data.tests, db_data.encoder, db_data.verges, 
            db_data.dbname,'127.0.0.1', 'root', db_data.secret)

async def computeAuxTables(db_data):
    if  db_data.type is not "discrete":
        async with vkf.Join(db_data.trains, db_data.dbname, '127.0.0.1', 
            'root', db_data.secret) as join:
            await join.compute_save(db_data.complex, db_data.dbname, 
                '127.0.0.1', 'root', db_data.secret)
        await vkf.Generator(db_data.complex, db_data.trains, db_data.verges, 
            db_data.dbname, db_data.dbname, db_data.verges_total, 1, 
            '127.0.0.1', 'root', db_data.secret)

async def induction(db_data, hypothesesNumber, threadsNumber):
    if  db_data.type is not "discrete":
        qualifier = await vkf.Qualifier(db_data.verges, 
            db_data.dbname, '127.0.0.1', 'root', db_data.secret)
        beget = await vkf.Beget(db_data.complex, db_data.dbname, 
            '127.0.0.1', 'root', db_data.secret)
    if  db_data.type is not "continuous":
        encoder = await vkf.Encoder(db_data.encoder, db_data.dbname, 
            '127.0.0.1', 'root', db_data.secret)
    async with vkf.Induction() as induction: 
        if  db_data.type is "continuous":
            await induction.load_continuous_hypotheses(qualifier, beget, 
                db_data.trains, db_data.hypotheses, db_data.dbname, 
                '127.0.0.1', 'root', db_data.secret)
        if  db_data.type is "discrete":
            await induction.load_discrete_hypotheses(encoder, 
                db_data.trains, db_data.hypotheses, db_data.dbname, 
                '127.0.0.1', 'root', db_data.secret)
        if  db_data.type is "full":
            await induction.load_full_hypotheses(encoder, qualifier, beget, 
                db_data.trains, db_data.hypotheses, db_data.dbname, 
                '127.0.0.1', 'root', db_data.secret)
        if hypothesesNumber > 0:
            await induction.add_hypotheses(hypothesesNumber, threadsNumber)
            if  db_data.type is "continuous":
                await induction.save_continuous_hypotheses(qualifier, 
                    db_data.hypotheses, db_data.dbname, '127.0.0.1', 'root', 
                    db_data.secret)
            if  db_data.type is "discrete":
                await induction.save_discrete_hypotheses(encoder, 
                    db_data.hypotheses, db_data.dbname, '127.0.0.1', 'root', 
                    db_data.secret)
            if  db_data.type is "full":
                await induction.save_full_hypotheses(encoder, qualifier, 
                    db_data.hypotheses, db_data.dbname, '127.0.0.1', 'root', 
                    db_data.secret)

async def prediction(db_data):
    if  db_data.type is not "discrete":
        qualifier = await vkf.Qualifier(db_data.verges, 
            db_data.dbname, '127.0.0.1', 'root', db_data.secret)
        beget = await vkf.Beget(db_data.complex, db_data.dbname, 
            '127.0.0.1', 'root', db_data.secret)
    if  db_data.type is not "continuous":
        encoder = await vkf.Encoder(db_data.encoder, 
            db_data.dbname, '127.0.0.1', 'root', db_data.secret)
    async with vkf.Induction() as induction: 
        if  db_data.type is "continuous":
            await induction.load_continuous_hypotheses(qualifier, beget, 
                db_data.trains, db_data.hypotheses, db_data.dbname, 
                '127.0.0.1', 'root', db_data.secret)
        if  db_data.type is "discrete":
            await induction.load_discrete_hypotheses(encoder, 
                db_data.trains, db_data.hypotheses, db_data.dbname, 
                '127.0.0.1', 'root', db_data.secret)
        if  db_data.type is "full":
            await induction.load_full_hypotheses(encoder, qualifier, beget, 
                db_data.trains, db_data.hypotheses, db_data.dbname, 
                '127.0.0.1', 'root', db_data.secret)
        if  db_data.type is "continuous":
            async with vkf.TestSample(qualifier, induction, beget, 
                db_data.tests, db_data.dbname, '127.0.0.1', 'root', 
                db_data.secret) as tests:
                #plus = await tests.correct_positive_cases()
                db_data.plus = await tests.correct_positive_cases()
                #minus = await tests.correct_negative_cases()
                db_data.minus = await tests.correct_negative_cases()
        if  db_data.type is "discrete":
            async with vkf.TestSample(encoder, induction, 
                db_data.tests, db_data.dbname, '127.0.0.1', 'root', 
                db_data.secret) as tests:
                #plus = await tests.correct_positive_cases()
                db_data.plus = await tests.correct_positive_cases()
                #minus = await tests.correct_negative_cases()
                db_data.minus = await tests.correct_negative_cases()
        if  db_data.type is "full":
            async with vkf.TestSample(encoder, qualifier, induction, 
                beget, db_data.tests, db_data.dbname, '127.0.0.1', 
                'root', db_data.secret) as tests:
                #plus = await tests.correct_positive_cases()
                db_data.plus = await tests.correct_positive_cases()
                #minus = await tests.correct_negative_cases()
                db_data.minus = await tests.correct_negative_cases()


J'ai enregistré ce fichier dans son intégralité, car ici vous pouvez voir les noms, l'ordre d'appel et les arguments des procédures de la méthode VKF à partir de la bibliothèque vkf.cpython-36m-x86_64-linux-gnu.so. Tous les arguments après dbname peuvent être omis, car les valeurs par défaut de la bibliothèque CPython sont définies avec des valeurs standard.



4. Commentaires



Anticipant la question des programmeurs professionnels de savoir pourquoi la logique de contrôle de l'expérience VKF est mise en évidence (à travers de nombreux ifs), et non cachée par le polymorphisme en types, la réponse devrait être la suivante: malheureusement, le typage dynamique du langage Python ne permet pas de déplacer la décision sur le type d'objet utilisé pour le système , c'est-à-dire, dans tous les cas, cette séquence de si imbriqués se produit. Par conséquent, l'auteur a choisi d'utiliser une syntaxe explicite (de type C) pour rendre la logique aussi transparente (et efficace) que possible.



Permettez-moi de commenter les composants manquants:



  1. vkfencoder.cpython-36m-x86_64-linux-gnu.so (web- , , ). vkfencoder.cpython-36m-x86_64-linux-gnu.so.
  2. - MariaDB ( DBeaver 7.1.1 Community, ). Django, ORM .


5.



L'auteur est engagé dans l'exploration de données depuis plus de 30 ans. Après avoir été diplômé de la Faculté de mécanique et de mathématiques de l'Université d'État de Moscou Lomonosov, il a été invité dans un groupe de chercheurs sous la direction du docteur en sciences techniques, prof. VK. Finn (VINITI AN SSSR). Depuis le début des années 80 du siècle dernier, Viktor Konstantinovich explore les raisonnements plausibles et leur formalisation au moyen de logiques à valeurs multiples.



Les idées clés proposées par V.K. Finn, les éléments suivants peuvent être envisagés:



  1. l'utilisation d'une opération de similarité binaire (à l'origine, l'opération d'intersection en algèbre booléenne);
  2. l'idée d'écarter la similitude générée d'un groupe d'exemples d'apprentissage si elle est intégrée dans la description d'un exemple du signe opposé (contre-exemple);
  3. l'idée de prédire les propriétés (cibles) étudiées de nouveaux exemples en tenant compte des avantages et des inconvénients;
  4. l'idée de vérifier l'exhaustivité de nombreuses hypothèses en trouvant les causes (parmi les similitudes générées) de la présence / absence de la propriété cible dans des exemples pédagogiques.


Il est à noter que V.K. Finn attribue certaines de ses idées à des auteurs étrangers. Peut-être que seule la logique de l'argumentation est légitimement considérée par lui comme étant inventée indépendamment. L'idée de rendre compte des contre-exemples par V.K. Finn a emprunté, selon lui, à K.R. Popper. Et les origines de la vérification de l'exhaustivité de la généralisation inductive lui appartiennent (complètement obscures, à mon avis) les travaux du mathématicien et logicien américain C.S. Transpercer. Il considère que la génération d'hypothèses sur les causes utilisant l'opération de similitude est empruntée aux idées de l'économiste, philosophe et logicien britannique D.S. Moulin. Par conséquent, il a créé un ensemble d'idées qu'il a intitulé «Méthode DSM» en l'honneur de D.S. Moulin.



Étrange, mais apparu à la fin des années 70 du XXe siècle dans les travaux du prof. Rudolf Wille (FRG), une section beaucoup plus utile de la théorie algébrique des réseaux "Analyse des concepts formels" (AFP) n'est pas utilisée par V.K. Salutations de Finn. À mon avis, la raison en est le nom malheureux, qui, comme une personne qui a d'abord obtenu son diplôme de la Faculté de philosophie, puis de l'ingénierie de la Faculté de mécanique et de mathématiques de l'Université d'État de Moscou, provoque le rejet.



En tant que successeur du travail de son professeur, l'auteur a nommé son approche «méthode VKF» en son honneur. Cependant, il existe un autre décodage - une méthode d'apprentissage formel combinatoire probabiliste basée sur la théorie du réseau.



Maintenant V.K. Finn travaille dans le CC eux. A.A. Dorodnicyn RAS FRC IU RAS et au Département des systèmes intelligents de l'Université d'État russe pour les sciences humaines.



Plus d'informations sur les mathématiques du solveur VKF peuvent être trouvées dans la thèse de l' auteur ou ses conférences vidéo à l'Université d'État d'Oulianovsk (l'auteur remercie A.B. Verevkin et N.G. Baranets pour l'organisation des conférences et le traitement de leurs notes).



Le package complet des fichiers sources est stocké sur Bitbucket .



Les fichiers source (en C ++) de la bibliothèque vkf sont en train de s'accorder sur leur emplacement sur savannah.nongnu.org. Si tel est le cas, un lien de téléchargement sera ajouté ici.



Enfin, une dernière remarque: l'auteur a commencé à apprendre Python le 6 avril 2020. Jusque-là, le seul langage dans lequel il avait programmé était le C ++. Mais cette circonstance ne supprime pas ses accusations d'une éventuelle inexactitude du code.



L'auteur remercie Tatyana A. Volkovarobofreakpour le support, les suggestions constructives et les critiques, qui ont permis d'améliorer significativement la présentation (et même d'améliorer significativement le code). Cependant, la responsabilité des erreurs restantes et des décisions prises (même contrairement à ses conseils) incombe uniquement à l'auteur.



All Articles