Servidor Web de aprendizado de máquina "VKF-solver"

Agora, aos olhos do público em geral, o aprendizado de máquina está fortemente associado a várias opções para o treinamento de redes neurais. Se inicialmente eram redes totalmente conectadas, então eram substituídas por convolucionais e recorrentes, agora se tornaram opções completamente exóticas, como redes GAN e LTSM. Além dos volumes crescentes de amostras necessárias para o treinamento, eles ainda sofrem com a impossibilidade de explicar por que essa ou aquela decisão foi tomada. Mas também existem abordagens estruturais para o aprendizado de máquina, cuja implementação de software é descrita neste artigo.







Essa é uma abordagem doméstica para o aprendizado de máquina, chamada método VKF de aprendizado de máquina baseado na teoria da rede. O histórico da ocorrência e a escolha do nome são explicados no final deste artigo.



1. Descrição do método



Inicialmente, todo o sistema foi criado pelo autor em C ++ como um aplicativo de console, depois foi conectado ao banco de dados sob o MariaDB DBMS (usando a biblioteca mariadb ++), depois foi transformado em uma biblioteca Python (usando o pacote pybind11).

Várias matrizes foram selecionadas como dados de teste para testar algoritmos de aprendizado de máquina no repositório da Universidade da Califórnia em Irvine.



No arranjo de cogumelos, contendo descrições de 8124 cogumelos norte-americanos, o sistema mostrou 100% de resultados. Mais precisamente, o gerador de números aleatórios dividiu os dados iniciais em um conjunto de treinamento (2088 comestíveis e cogumelos venenosos de 1944) e um conjunto de testes (2120 comestível e 1972 venenoso). Após calcular cerca de 100 hipóteses sobre os motivos da comestibilidade, todos os casos de teste foram previstos corretamente. Como o algoritmo usa uma cadeia de Markov emparelhada, um número suficiente de hipóteses pode variar. Muitas vezes, foi o suficiente para gerar 50 hipóteses aleatórias. Observe que, ao gerar as causas da toxicidade, o número de hipóteses necessárias é agrupado em torno de 120, no entanto, todos os casos de teste são previstos corretamente nesse caso. Kaggle.com tem um concurso de Classificação de cogumelosonde alguns autores alcançaram 100% de precisão. Mas a maioria das soluções são redes neurais. Nossa abordagem permite que o coletor de cogumelos aprenda apenas cerca de 50 regras. Como a maioria dos recursos é insignificante, cada hipótese será uma conjunção de um pequeno número de valores de recursos essenciais, o que os torna fáceis de lembrar. Depois disso, o coletor de cogumelos pode buscar cogumelos sem medo de pegar um cogumelo venenoso ou perder um cogumelo comestível.



Aqui está um exemplo de uma das hipóteses com base nas quais o cogumelo pode ser considerado comestível:

[('gill_attachment', 'free'), ('gill_spacing', 'close'), ('gill_size', 'wide'), ('stalk_shape ',' ampliando '), (' stalk_surface_below_ring ',' scaly '), (' veil_type ',' parcial '), (' veil_color ',' white '), ('número do anel ',' um '), (' tipo de anel ',' pendente ')]



Chamo a atenção para o fato de que apenas 9 dos 22 sinais são mostrados na lista, pois os outros 13 sinais de similaridade não são observados nos cogumelos comestíveis que deram origem a esse motivo.



Outra matriz foi o SPECT Hearts. Lá, a precisão da previsão dos casos de teste atingiu 86,1%, o que acabou sendo um pouco maior que os resultados (84%) do sistema de aprendizado de máquina CLIP3, com base na cobertura de exemplos de treinamento usando programação inteira, usada pelos autores da matriz. Acredito que, devido à estrutura da descrição dos tomogramas do coração, que já são pré-codificados com sinais binários, não é possível melhorar significativamente a qualidade da previsão.



Recentemente, o autor apresentou (e implementou em software) uma extensão de sua abordagem ao processamento de dados descrita por recursos (numéricos) contínuos. Em alguns aspectos, sua abordagem é semelhante ao sistema C4.5 de aprendizagem de árvores de decisão. Essa variante foi testada na matriz Wine Quality. Esta matriz descreve a qualidade dos vinhos portugueses. Os resultados são encorajadores: se você tomar vinhos tintos de alta qualidade, as hipóteses explicam completamente suas pontuações mais altas.



2. Seleção de plataforma



Atualmente, através dos esforços dos estudantes do Departamento de Sistemas Inteligentes da Universidade Estatal Russa de Ciências Humanas, está sendo criada uma série de servidores da web para vários tipos de tarefas (usando o pacote Nginx + Gunicorn + Django).



No entanto, decidi descrever minha versão pessoal aqui (usando os pacotes aiohttp, aiojobs e aiomysql). O módulo aiomcache não é usado devido a problemas de segurança conhecidos.



Existem várias vantagens na opção proposta:



  1. é assíncrono devido ao uso de aiohttp;
  2. permite modelagem de Jinja2;
  3. Funciona com o conjunto de conexões do banco de dados através do aiomysql;
  4. ele permite que processos computacionais independentes sejam iniciados via aiojobs.aiohttp.spawn.


Vamos também apontar as desvantagens óbvias (em comparação com o Django):



  1. nenhum mapeamento relacional de objetos (ORM);
  2. mais difícil organizar o uso do servidor proxy Nginx;
  3. no Django Template Language (DTL).


Cada uma das duas opções visa estratégias diferentes para trabalhar com um servidor da web. A estratégia síncrona (no Django) visa um modo de usuário único, no qual um especialista trabalha com um único banco de dados de cada vez. Embora os procedimentos probabilísticos do método CCF sejam notavelmente paralelizados, no entanto, teoricamente, um caso não é excluído quando os procedimentos de aprendizado de máquina levam um tempo considerável. Portanto, a opção discutida nesta nota é destinada a vários especialistas, cada um dos quais pode trabalhar simultaneamente (em diferentes guias do navegador) com diferentes bancos de dados que diferem não apenas nos dados, mas também na forma como são apresentados (treliças diferentes nos valores de recursos discretos, diferentes regressões significativas e número). limiares para contínuo). Em seguida, ao iniciar o experimento CCF em uma guia, o especialista pode mudar para outra,onde preparará ou analisará um experimento com diferentes dados e / ou parâmetros.



Para contabilizar vários usuários, experimentos e diferentes estágios em que estão localizados, existe um banco de dados de serviço (vkf) com duas tabelas (usuários, experimentos). Se a tabela de usuários armazenar o login e a senha de todos os usuários registrados, as experiências, além dos nomes das tabelas auxiliar e principal de cada experiência, manterão o status dessas tabelas sendo preenchidas. Abandonamos aiohttp_session, pois ainda precisamos usar um proxy Nginx para proteger dados críticos.



Aqui está a estrutura da tabela de experimentos:



  • id int (11) NÃO NULL CHAVE PRIMÁRIA
  • expName varchar (255) NÃO NULL
  • codificador varchar (255)
  • goodEncoder tinyint (1)
  • retículo varchar (255)
  • Retrospectiva tinyint (1)
  • varchar complexo (255)
  • goodComplex tinyint (1)
  • verges varchar (255)
  • goodVerges tinyint (1)
  • total int (11)
  • trens varchar (255) NÃO NULL
  • GoodTrains tinyint (1)
  • tests varchar(255)
  • goodTests tinyint(1)
  • hypotheses varchar(255) NOT NULL
  • goodHypotheses tinyint(1)
  • type varchar(255) NOT NULL


Deve-se notar que existem algumas seqüências de preparação de dados para experimentos de CCF, que, infelizmente, são radicalmente diferentes para os casos discretos e contínuos. O caso de atributos mistos combina requisitos de ambos os tipos.



discreto: => goodLattices (semi-automático)

discreto: goodLattices => goodEncoder (automático)

discreto: goodEncoder => goodTrains (semiautomático)

discreto: goodEncoder, goodTrains => goodHypotheses (automático)

discreto: goodEncoder => goodTests (semiautomático),

discreto goodEncoder, goodHypotheses => (automático)

contínuo: => goodVerges (manual)

contínuo: goodVerges => goodTrains (manual)

contínuo: goodTrains => goodComplex (automático)

contínuo: goodComplex, goodTrains => goodHypotheses (automático)

contínuo: goodVerges => goodTests (manual)

contínuo: goodTests, goodComplex, goodHypotheses => (automático)



A própria biblioteca de aprendizado de máquina é chamada vkf.cpython -36m-x86_64-linux-gnu.so no Linux ou vkf.cp36-win32.pyd no Windows. (36 é a versão do Python para a qual esta biblioteca foi criada).



O termo "automático" significa o trabalho desta biblioteca, "semi-automático" significa o trabalho da biblioteca auxiliar vkfencoder.cpython-36m-x86_64-linux-gnu.so. Finalmente, o modo "manual" é uma chamada de programas que processam especialmente os dados de um experimento específico e agora são transferidos para a biblioteca vkfencoder.



3. Detalhes da Implementação



Ao criar um servidor Web, usamos a abordagem "Exibir / Modelar / Controlar"



. O código Python está localizado em 5 arquivos:



  1. app.py - arquivo de inicialização do aplicativo
  2. control.py - arquivo com procedimentos para trabalhar com o solucionador VKF
  3. models.py - arquivo com classes para processar dados e trabalhar com o banco de dados
  4. settings.py - arquivo com configurações do aplicativo
  5. views.py - arquivo com visualização e manipulação de rotas (rotas).


O arquivo app.py fica assim:



#! /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()


Acho que não há nada para explicar aqui. O próximo arquivo na ordem de inclusão no projeto é 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()


Reduzi esse arquivo para a presente nota lançando classes que servem rotas utilitárias:



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


As demais turmas processam as rotas responsáveis ​​pelas etapas do aprendizado de máquina:



  1. a classe Prepare processa as rotas '/ experiment / / {name}' e coleta os nomes das tabelas de serviço e os parâmetros numéricos necessários para executar os procedimentos do método VKF. Depois de salvar essas informações no banco de dados, o usuário é redirecionado para a rota '/ vkf / {name}'.
  2. a classe Generate processa as rotas '/ vkf / {name}' e inicia vários estágios do procedimento de indução do método VKF, dependendo da preparação dos dados pelo especialista.
  3. a classe Predict processa rotas '/ tests / {name}' e inicia o procedimento do método de previsão VKF por analogia.


Para passar um grande número de parâmetros para o formulário vkf.html, uma construção de aiohttp_jinja2 é usada



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




Observe também o uso da chamada de spawn do pacote aiojobs.aiohttp:



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


Isso é necessário para chamar com segurança as corotinas das classes definidas no arquivo models.py que processam os dados do usuário e do experimento armazenados em um banco de dados gerenciado pelo 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)




Novamente, algumas classes auxiliares estão ocultas:



  1. A classe Usuário corresponde ao visitante do site. Ele permite que você se registre e faça login como especialista.
  2. A classe Expert permite que você escolha uma das experiências.


As demais classes correspondem aos principais procedimentos:



  1. A classe Experiment permite especificar os nomes das tabelas chave e auxiliares e os parâmetros necessários para a realização de experimentos da CIF.
  2. A classe Solver é responsável pela generalização indutiva no método VKF.
  3. A classe Predictor é responsável por previsões por analogia no método CCF.


É importante observar o uso da construção create_pool () do pacote aiomysql. Ele permite que você trabalhe com um banco de dados em várias conexões. As rotinas assegurar_futuro () e reunir () do módulo asyncio também são necessárias para aguardar a conclusão da execução.



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()


Ao ler de uma tabela, row = cur.fetchone () retorna um futuro, portanto row.result () retorna um registro do banco de dados do qual os valores do campo podem ser recuperados (por exemplo, str (row.result () [2]) recupera o nome da tabela com codificação dos valores de recursos discretos).




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])


Os principais parâmetros do sistema são importados do arquivo .env ou (se ausente) do arquivo 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)


É importante observar que o host local deve ser especificado pelo endereço IP; caso contrário, o aiomysql tentará se conectar ao banco de dados por meio de um soquete Unix, que pode não funcionar no Windows. Por fim, vamos reproduzir o último arquivo (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()


Salvei este arquivo na íntegra, pois aqui você pode ver os nomes, a ordem de chamada e os argumentos dos procedimentos do método VKF da biblioteca vkf.cpython-36m-x86_64-linux-gnu.so. Todos os argumentos após dbname podem ser omitidos, pois os padrões na biblioteca CPython são configurados com valores padrão.



4. Comentários



Antecipando a pergunta de programadores profissionais sobre por que a lógica de controlar o experimento VKF é apresentada (através de vários ifs) e não oculta através de polimorfismo em tipos, a resposta deve ser a seguinte: infelizmente, a digitação dinâmica da linguagem Python não permite mudar a decisão sobre o tipo de objeto usado no sistema , ou seja, essa sequência de ifs aninhados ocorrerá de qualquer maneira. Portanto, o autor preferiu usar uma sintaxe explícita (semelhante a C) para tornar a lógica o mais transparente (e eficiente) possível.



Deixe-me comentar sobre os componentes ausentes:



  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.



O autor está envolvido em tarefas de mineração de dados há mais de 30 anos. Depois de se formar na Faculdade de Mecânica e Matemática da Universidade Estadual de Moscou, em homenagem a M.V. Lomonosov, ele foi convidado para um grupo de pesquisadores liderados pelo Doutor em Ciências Técnicas, prof. VK. Finn (VINITI UMA URSS). Desde o início dos anos 80 do século passado, Viktor Konstantinovich explora o raciocínio plausível e sua formalização por meio de lógicas de valores múltiplos.



As ideias-chave propostas por V.K. Finn, o seguinte pode ser considerado:



  1. usando uma operação de similaridade binária (inicialmente, a operação de interseção na álgebra booleana);
  2. a idéia de descartar a semelhança gerada de um grupo de exemplos de treinamento se ele estiver incorporado na descrição de um exemplo do sinal oposto (contra-exemplo);
  3. a ideia de prever as propriedades investigadas (alvo) de novos exemplos, levando em consideração os prós e os contras;
  4. a idéia de verificar a integridade de um conjunto de hipóteses encontrando razões (entre as semelhanças geradas) para a presença / ausência de uma propriedade de destino nos exemplos de treinamento.


Deve-se notar que V.K. Finn atribui algumas de suas idéias a autores estrangeiros. Talvez apenas a lógica da argumentação seja legitimamente considerada por ele inventada independentemente. A ideia de contabilizar contra-exemplos por V.K. Finn pediu emprestado, segundo ele, à K.R. Popper. E as origens de verificar a completude da generalização indutiva pertencem a ele (completamente obscuro, na minha opinião), as obras do matemático e lógico americano C.S. Pier. Ele considera que a geração de hipóteses sobre as causas usando a operação de similaridade deve ser emprestada das idéias do economista, filósofo e lógico britânico D.S. Moinho. Portanto, ele criou um conjunto de idéias que intitulou "método DSM" em homenagem a D.S. Moinho.



Estranho, mas surgindo no final dos anos 70 do século XX nos escritos do prof. Rudolph Ville (Alemanha) não usa uma seção muito mais útil da teoria algébrica das redes "Análise de conceitos formais" (AFP) em V.K. Os cumprimentos de Finn. Na minha opinião, a razão para isso é um nome infeliz, que, como uma pessoa que se formou pela Faculdade de Filosofia e depois o fluxo de engenharia da Faculdade de Mecânica e Matemática da Universidade Estadual de Moscou, causa rejeição.



Como sucessor do trabalho de seu professor, o autor nomeou sua abordagem "método VKF" em sua homenagem. No entanto, há outra decodificação - um método formal combinatório probabilístico de aprendizado de máquina baseado na teoria da rede.



Agora, o grupo de V.K. Finna trabalha no centro de exposições. A.A. Dorodnicyn RAS FRC IU RAS e no Departamento de Sistemas Inteligentes da Universidade Estatal Russa de Ciências Humanas.



Mais informações sobre a matemática do solucionador de VKF podem ser encontradas na dissertação do autor ou em suas palestras em vídeo na Universidade Estadual de Ulyanovsk (por organizar palestras e processar suas anotações, o autor agradece a A.B. Verevkin e N.G. Baranets).



O pacote completo de arquivos de origem é armazenado no Bitbucket .



Os arquivos de origem (em C ++) da biblioteca vkf estão concordando com sua colocação em savannah.nongnu.org. Nesse caso, um link para download será adicionado aqui.



Por fim, uma observação final: comecei a aprender Python em 6 de abril de 2020. Antes disso, a única linguagem na qual ele programava era C ++. Mas essa circunstância não remove suas acusações de possível imprecisão do código.



O autor gostaria de agradecer a Tatyana A. Volkovarobofreakpor apoio, sugestões construtivas e críticas, que possibilitaram melhorar significativamente a apresentação (e até melhorar significativamente o código). No entanto, a responsabilidade pelos erros restantes e pelas decisões tomadas (mesmo que contrárias aos seus conselhos) é exclusivamente do autor.



All Articles