Neste tutorial, desenvolvemos um pequeno e simples aplicativo web utilizando ferramentas gratuitas. Usamos Python e recursos de aprendizagem de máquina (machine learning - ML) para realizar análises de suspeitas de plágio utilizando inteligência artificial (IA). Além disso, colocamos o app em produção, disponibilizando seu acesso para qualquer computador na web!
Vamos lá? ☕
1. O que vc acha do mini app abaixo?
💻 Reflexão: Como o app mostrado acima foi desenvolvido? Podemos criar nosso próprio app em Python usando tecnologias gratuitas (100% free)? Há uma maneira pratica de embutir funcionalidades com aprendizagem de máquina num app?
Se sua resposta foi sim
para as três perguntas, talvez este post contribua mais para vc na parte do caso de estudo. Se vc está entre o talvez
ou não
como resposta em alguma delas, é bem provável que este post irá contribuir para sua bagagem em desenvolvimento e deploy de projetos com ferramentas Python.🍵
1.1. O que o app fará?
O web app que desenvolveremos e colocaremos em produção no Deta Space, serve para identificar se dois textos são "iguais" ou "não". Quem irá materializar isso que chamamos de "iguais", será um modelo de inteligência artificial (IA) gratuito e disponível no site da Hugging Face. Acessaremos o modelo de IA por meio de uma API (Application Programming Interface).
1.2. O que é feito neste tutorial (PARTE 1 e PARTE 2) ?
Configuração de um ambiente virtual com Python para rodar o micro-framework Flask (PARTE 1)
Desenvolvimento da parte lógica do app usando os padrões de templates, rotas e funções de visualização do Flask (PARTE 1)
Como embutir um modelo de aprendizagem de máquina (ML) no app (PARTE 1)
Realizar o deploy do app em um ambiente de produção (PARTE 2)
2. Como rodar localmente o app feito com o Flask?
No desenvolvimento de software em Python é comum o uso de um ambiente virtual (virtual environment, venv
) para isolar as bibliotecas que são pré-requisitos. As bibliotecas Python e suas dependências costumam funcionar sem conflitos quando se usa um ambiente virtual específico durante o desenvolvimento.
2.1. Criação e ativação do ambiente virtual local
Primeiramente, vamos criar uma pasta/diretório com o nome-do-app
:
mkdir nome-do-app
cd nome-do-app
Fique à vontade para colocar o nome que desejar ao invés de nome-do-app
. Esse será nosso diretório raiz.
Dentro da pasta criada (nome-do-app
), criaremos o ambiente virtual. Para isso usamos o seguinte comando que deve ser digitado em um terminal
(bash
, shell
ou prompt
) que possua o Python 3 instalado:
python -m venv venv_app
Após criar o ambiente virtual (venv_app
), precisamos ativá-lo:
- no Linux:
source venv_app/bin/activate
- no Windows:
venv\Scripts\activate
Nota: Os arquivos que estão dentro de venv_app
são gerados automaticamente no momento em que criamos o ambiente virtual. Portanto, não precisamos nos preocupar com eles.
2.2 Instalação das dependências
Agora que já estamos com o ambiente virtual local ativado, devemos instalar neste ambiente os pré-requisitos (requirements.txt
) que serão usados.
O conteúdo do arquivo requirements.txt
é:
# requirements.txt
Flask
Flask-Bootstrap # extensão para bootstrap (css, styles, navbar, etc)
Flask-Moment # extensão para manipular datetime
Flask-WTF # extensão para web forms
python-dotenv # gerenciador de variáveis env
requests
waitress
Para instalar no Python as bibliotecas desse arquivo .txt
digitamos o seguinte comando no terminal:
pip install -r requirements.txt
Estrutura dos arquivos até o momento:
nome-do-app/
│
├── venv_app/
│
└── requirements.txt
3. Primeira versão do app
Seguindo, mais ou menos, a documentação do Flask, agora criaremos os templates (*.html
), os endpoints e as funções de visualização como parte do código para o app. Com esses esses três elementos conseguimos implementar a lógica de negócio que desejarmos no app.
Os usuários vão chamar/rodar as funcionalidades do app através da inserção de textos num elemento html chamado TextArea
e de cliques em um botão
html (<button>
).
A parte visual do app será feita com a extensão do pacote Bootstrap para o Flask (Flask-Bootstrap
).
Nota: É usual, ao invés de usarmos diretamente uma ferramenta como por ex., o Bootstrap, usarmos sua preferir sua extensão para o Flask (quando houver a extensão). Isso ajuda a integrar Flask e outras ferramentas.
3.1. Script principal (app.py
)
Agora devemos criar o seguinte arquivo Python, chamado app.py
, na raiz da pasta nome-do-app
. É possível criar o arquivo manualmente, através de cliques, ou, se tivermos no Linux, pelo terminal com:
touch app.py
Esse é o código de app.py
:
# app.py
## 1. carrega bibliotecas
from flask import Flask, render_template, url_for, session, redirect, flash
from flask_bootstrap import Bootstrap
from flask_moment import Moment
from flask_wtf import FlaskForm
from wtforms import SubmitField, TextAreaField
from wtforms.validators import InputRequired, Length
import os
from datetime import datetime
from utils.constants import *
import random
app = Flask(__name__)
app.config['SECRET_KEY'] = '12345'
bootstrap = Bootstrap(app)
moment = Moment(app)
N_RANDOM = random.randint(7, 300)
##
## 2. funções do modelo de ml
def preprocess_text(some_text: str):
some_text_processed = ' '.join([word for word in some_text.split(' ') if len(word)])
return some_text_processed
def run_model(original_text, suspect_text):
import json
import requests
API_URL = os.environ.get('API_URL')
API_TOKEN = os.environ.get('API_TOKEN')
headers = {'Authorization': f'Bearer {API_TOKEN}'}
payload = {'inputs': {'source_sentence': original_text, 'sentences': [suspect_text]}}
try:
response = requests.post(API_URL, headers=headers, json=payload)
res = response.json()
except Exception as e:
res = [-1*random.randint(0, 100)/100] # para testes
print(f'\nErro:\n{e}\n')
return res
def check_plagiarism(original_text: str, suspect_text: str):
is_fake = False
original_text_clean = preprocess_text(original_text)
suspect_text_clean = preprocess_text(suspect_text)
plagiarism_res = run_model(original_text_clean, suspect_text_clean)
N_MAX_RETRY = 3
n_retry = 1
if type(plagiarism_res) == dict:
while 'error' in plagiarism_res.keys():
import time
try:
time_waiting = 0.7*plagiarism_res["estimated_time"]
except:
time_waiting = 3
time.sleep(time_waiting)
plagiarism_res = run_model(original_text_clean, suspect_text_clean)
n_retry += 1
if n_retry >= N_MAX_RETRY:
plagiarism_probab = -1*random.randint(0, 100)/100 # para testes
break
if type(plagiarism_res) != dict:
plagiarism_probab = plagiarism_res[0]
break
else:
plagiarism_probab = plagiarism_res[0]
return round(plagiarism_probab, 2)
##
## 3. construtor para o objeto formulário
class NameForm(FlaskForm):
original_text = TextAreaField('Texto Original',
validators=[InputRequired(), Length(7, 1000)],
render_kw={'class': 'form-control', 'rows':5,
'placeholder': '\nEntre com um texto aqui. Por ex.:\n' + LOREM_IPSUM[:N_RANDOM] + ' ...'})
suspect_text = TextAreaField('Texto Suspeito de Plágio',
validators=[InputRequired(), Length(7, 1000)],
render_kw={'class': 'form-control', 'rows':4,
'placeholder': '\nEntre com outro texto aqui.'})
submit = SubmitField('Checar')
##
## 4. endpoints e funções de visualização
@app.route('/', methods=['GET', 'POST'])
def index():
form = NameForm()
is_fraud = -1
session['original_text'] = form.original_text.data
session['suspect_text'] = form.suspect_text.data
if form.validate_on_submit():
original_text = session.get('original_text')
suspect_text = session.get('suspect_text')
plagiarism_probab = check_plagiarism(original_text, suspect_text)
# para testes
emph = ""
if plagiarism_probab < 0:
plagiarism_probab *= -1
emph = '"'
# checa probab
if plagiarism_probab > 0.5:
is_fraud = 1
print('Parece plágio!')
elif plagiarism_probab >= 0:
is_fraud = 0
print('Não parece plágio!')
flash(f'\nTexto {emph}checado{emph}! {emph}Probabilidade de plágio{emph} = {plagiarism_probab*100:.2f} %.\n')
return render_template('index.html', form=form,
user_name=session.get('original_text'), current_time=datetime.utcnow(),
is_fraud=is_fraud)
return render_template('index.html', form=form,
user_name=session.get('original_text'), current_time=datetime.utcnow(),
is_fraud=is_fraud)
@app.route('/how_to')
def how_to():
return render_template('how_to.html', current_time=datetime.utcnow())
##
if __name__ == '__main__':
app.run(debug=True)
O script app.py
, mostrado acima, tem 4 partes principais:
- 1) carrega bibliotecas;
- 2) funções do modelo de ML;
- 3) construtor para o objeto formulário;
- 4) endpoints/rotas e funções de visualização.
A parte 1 carrega as bibliotecas Python que usaremos. Como por ex., as extensões que facilitam a implementação de recursos do Bootstrap (from flask_bootstrap import Bootstrap
) e a manipulação segura de formulários (from flask_wtf import FlaskForm
).
A parte 2 é onde embutimos o modelo de ML/IA no app. Adiante, falaremos um pouco mais desse modelo.
Já na parte 3 configuramos os formulários do app.
Há benefícios/facilidades quando os formulários podem representados por objetos Python (como aqui) ao invés de se implementar apenas com html. Por ex., fica mais prático manipular os atributos de um formulário e incluir camadas de seguranças.
Por fim, na parte 4 definimos os endpoints (/
, /how_to
) e suas respectivas funções de visualização (index()
e how_to()
).
3.2. Separando as utilidades
Como usamos a chamada from utils.constants import *
para deixar o código minimamente organizado, devemos criar a pasta utils
e dentro desta o arquivo constants.py
:
# constants.py
LOREM_IPSUM = '''
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Elementum nibh tellus molestie nunc non blandit massa enim nec. Egestas maecenas pharetra convallis posuere morbi leo urna molestie.
'''
Caso esteja usando um terminal Linux, podemos usar os seguintes comandos:
mkdir utils
touch utils/constants.py
Estrutura atualizada dos arquivos do projeto:
nome-do-app/
│
├── utils/
│ └── constants.py
│
├── venv_app/
│
├── requirements.txt
└── app.py
3.2. Templates (*.html
)
Usaremos três templates para a construção das telas do app: base.html
; index.html
e how_to.html
.
Como o próprio nome diz, o base.html
servirá de base para os outros templates html. Nesse arquivo usamos recursos do Bootstrap
para que a navegação no app seja a melhor possível.
Já os templates index()
e how_to()
, são aqueles chamados diretamente pelo app.py
. O primeiro organiza as chamadas da tela principal (Home
), enquanto o segundo exibe uma imagem animada explicando como usar o app.
Nota: Embora não haja muitos segredos, a definição de plágio pode variar, se é que existe uma única definição. Talvez, variáveis como todos os textos do mundo, influência política, legislação do país e compromisso científico/"vergonha na cara" possam ser levadas em conta na modelagem.
🤖 No entanto, priorizamos pela simplicidade. Portanto, a funcionalidade principal do app é que ele possa realizar a comparação automática (com IA) entre um texto original e um outro suspeito de plágio
deste original.
📄 Poderemos incluir melhorias e correções depois que a primeira versão do app estiver em produção. Então, sem mais delongas, na raiz do projeto criemos uma pasta chamada templates
.
mkdir templates
A seguir, devemos criar os arquivos base.html
, index.html
e how_to.html
dentro da pasta templates
. No Linux, podemos novamente usar o comando touch
:
touch templates/base.html templates/index.html templates/how_to.html
Abaixo temos os conteúdos dos arquivos *.html
. Note que, como usamos o Flask, esses contêm códigos de Jinja
. Essa engine/"linguagem" permite a manipulação de variáveis, listas, objetos, etc. misturando Python e html.
<!-- base.html -->
{% extends 'bootstrap/base.html' %}
{% block title%}{% endblock %}
{% block head %}
{{ super() }}
<link rel="shortcut icon" href="{{ url_for('static', filename='favicon.ico') }}" type="image/x-icon">
<link rel="icon" href="{{ url_for('static', filename='favicon.ico') }}" type="image/x-icon">
{% endblock %}
{% block scripts %}
{{ super() }}
{{ moment.include_moment() }}
{{ moment.locale('pt') }}
{% endblock %}
{% block navbar %}
<ul class="nav nav-pills col-md-3 col-md-offset-8" style="margin-top:5px">
<li role="presentation" class="active"><a href="{{ url_for('index') }}">Home</a></li>
<li role="presentation" ><a href="{{ url_for('how_to') }}">Como Usar</a></li>
<li role="presentation" class="disabled"><a href="#">Sobre</a></li>
</ul>
{% endblock %}
{% block content %}
<div class="container">
<div class="page-header">
<h3>Entre com os textos a serem comparados:</h3>
{{ wtf.quick_form(form) }}
</div>
</div>
<div class="container col-md-6 col-md-offset-3">
{% if is_fraud != -1%}
{% if is_fraud == 1 %}
<p><center><b>Parece plágio!</b></center></p>
<link rel="shortcut icon" href="{{ url_for('static', filename='no.ico') }}" type="image/x-icon">
<link rel="icon" href="{{ url_for('static', filename='no.ico') }}" type="image/x-icon">
{% elif is_fraud == 0 %}
<p><center><b>Não parece plágio!</b></center></p>
<link rel="shortcut icon" href="{{ url_for('static', filename='yes.ico') }}" type="image/x-icon">
<link rel="icon" href="{{ url_for('static', filename='yes.ico') }}" type="image/x-icon">
{% endif %}
{% endif %}
</div>
</div>
{% for message in get_flashed_messages() %}
<div class="alert alert-warning col-md-6 col-md-offset-3">
<button type="button" class="close" data-dismiss="alert">×</button>
{{ message }}
</div>
{% endfor %}
{% endblock %}
<hr>
<br>
{% block body %}
{{ super() }}
<div class="container col-md-6 col-md-offset-3">
<div>
<small>
<p><center>{{ moment(current_time).format('LL') }}</center></p>
</small>
</div>
</div>
{% endblock %}
<!-- index.html -->
{% extends 'base.html' %}
{% import "bootstrap/wtf.html" as wtf %}
{% block title%}Checador de Plágio Simples - versão β{% endblock %}
{% block flash_message %}{% endblock %}
<!-- how_to.html -->
{% extends 'bootstrap/base.html' %}
{% block title%}Como Usar o Checador de Plágio Simples - versão β{% endblock %}
{% block navbar %}
<ul class="nav nav-pills col-md-3 col-md-offset-8" style="margin-top:5px">
<li role="presentation"><a href="{{ url_for('index') }}">Home</a></li>
<li role="presentation" class="active"><a href="{{ url_for('how_to') }}">Como Usar</a></li>
<li role="presentation" class="disabled"><a href="#">Sobre</a></li>
</ul>
{% endblock %}
{% block content %}
<div class="container">
<div class="container col-md-9 col-md-offset-1">
<p style="margin-top:7px"><h4>Siga os passos abaixo para usar este app.</h4><p>
<div class="thumbnail">
<img src="{{ url_for('static', filename='how_to.gif') }}">
{# <img src="{{ url_for('static', filename='how_to.gif') }}" style="max-width:40%; height:auto;"> #}
</div>
</div>
</div>
{% endblock %}
Após todas as etapas que fizemos até o momento, ficamos com a seguinte estrutura de arquivos na pasta raiz nome-do-app
:
nome-do-app/
│
├── templates/
│ ├── base.html
│ ├── index.html
│ └── how_to.html
│
├── utils/
│ └── constants.py
│
├── venv_app/
│
├── requirements.txt
└── app.py
3.3. Inserindo imagens
Nos templates *.html, foi usado uma imagem (*.gif
) e ícones (*.ico
) que aparecem na barra de título do html. Para usar esses arquivos, criamos uma pasta static
na raiz do projeto (nome-do-app
) e colocamos os mesmos dentro dessa pasta.
Download das imagens e ícones: Baixe as imagens do link abaixo ou use algumas de sua preferência: link para baixar imagens e ícones.
nome-do-app/
│
├── static
│ ├── favicon.ico
│ ├── how_to.gif
│ ├── no.ico
│ └── yes.ico
│
├── templates/
│ ├── base.html
│ ├── index.html
│ └── how_to.html
│
├── utils/
│ └── constants.py
│
├── venv_app/
│
├── requirements.txt
└── app.py
3.4. Rodar o app localmente
Agora o app está quase pronto. Para rodar a versão atual do app, digite o seguinte comando no terminal:
python app.py
Na sequência, se tudo der certo, vc poderá ver a tela principal do app no endereço e porta padrões do Flask: http://127.0.0.1:5000.
4. Configurando a API de ML ⌨️
No arquivo app.py
há constantes que foram usadas para acessarmos a API da Hugging Face, responsável pela checagem do nível de semelhança entre os dois textos de entrada. Se os textos forem muito semelhantes, há uma alta probabilidade de plágio.
Nota: No site da Hugging Face há inúmeros modelos de ML, casos de uso e datasets que podem servir como base para novos/futuros projetos. Por ex., atualmente, na área de Processamento de Linguagem Natural (NLP, em inglês), eles disponibilizam modelos para as mais diferentes tarefas na área.
4.1. Criando uma conta gratuita na Hugging Face
Lembrando, o modelo de aprendizagem de máquina escolhido para nosso app deverá ter habilidades de verificar a semelhança entre dois textos. A Hugging Face disponibiliza um modelo que pode ajudar nessa tarefa, conhecido como Sentence Similarity.
Como usar um modelo da Hugging Face?
Para usarmos um modelo de Sentence Similarity da Hugging Face via API precisamos ter um token de acesso. Após criarmos uma conta (gratuita) no site da Hugging Face, teremos acesso ao token.
Esse token é usado para se fazer requisições a API, conforme descrito no site deles.
...
API_URL = 'https://api-inference.huggingface.co/models/sentence-transformers/all-MiniLM-L6-v2'
headers = {'Authorization': f'Bearer {API_TOKEN}'}
...
Com o token e a URL de acesso ao modelo em mãos já podemos usar a API da Hugging Face.
🤔 Se incluirmos de forma explícita (hardcoded) os valores das constantes API_URL
e API_TOKEN
em nosso programa principal app.py
, já conseguiremos realizar as avaliações da suspeita de plágio usando IA via API!
Nota: Mais detalhes sobre o modelo aqui escolhido podem ser encontrados em: https://huggingface.co/tasks/sentence-similarity.
4.2. Definindo as variáveis ambientes (.env
)
Para inserir os valores de API_URL
e API_TOKEN
no arquivo app.py
seguindo as boas práticas iremos criar um arquivo chamado .env
na raiz do projeto.
Ressaltamos que para cada usuário, teremos um token diferente. O token é um valor secreto e não é recomendável que ele faça parte dos códigos. Além do token, há valores que não são recomendados fazerem parte do código, mas serem chamados de forma implícita.
Como colocaremos o app desenvolvido em produção, seguiremos as recomendações de boas práticas sempre que possível. O arquivo .env
deverá ser criado na raiz do projeto e ter a seguinte forma:
# .env
SECRET_KEY_FLASK='!@$%&<mudar-para-uma-secret-key>'
API_URL='https://api-inference.huggingface.co/models/sentence-transformers/all-MiniLM-L6-v2'
API_TOKEN='123456789!@$%&<mudar-para-seu-token>'
Como agora definimos o valor de SECRET_KEY
no arquivo .env
, devemos retirá-lo do app.py
. Para isso, alteramos a seguinte linha neste arquivo:
- De:
app.config['SECRET_KEY'] = '12345'
- Para:
app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY_FLASK')
Atenção: O valor de API_TOKEN
deve ser alterado para o valor da sua conta na Hugging Face conforme mostramos anteriormente.
Após criar o arquivo .env
devemos incluir os seguintes comandos em algum lugar no início do arquivo app.py
, de forma que os valores de SECRET_KEY
, API_URL
e API_TOKEN
agora fique disponível no app.py
, não sendo mais um valor nulo.
# app.py
...
from dotenv import load_dotenv, find_dotenv
load_dotenv(find_dotenv())
...
Os 3 pontinhos no trecho de código acima indicam que o restante do código permanece sem mudanças.
4.3. Preparando para o deploy
Antes de enviar para deploy em produção, devemos verificar se está tudo ok. Para isso rodamos o app desenvolvido localmente (python app.py
) e acessamos o localhost (http://127.0.0.1:5000
) para navegar nele.
Boas práticas (novamente):
A fim de seguirmos as boas práticas para por um app em produção, ao invés de rodarmos o app chamando diretamente com python app.py
, como comumemente é feito na etapa de desenvolvimento, usaremos o WSGI (Web Server Gateway Interface) waitresss
como mediador da comunicação entre do servidor e o app em Flask. Com isso o app vai para produção de acordo com as recomendações.
Note que, o waitress
já está presente no requirements.txt
. Com o waitress
ao invés de chamarmos diretamente o app.py
para rodar nossa aplicação, iremos chamar o seguinte arquivo main.py
, que deve ser criado na raiz do projeto:
# main.py
from waitress import serve
from app import *
if __name__ == '__main__':
local_host, local_port = '0.0.0.0', 8080
print(f'\nServindo em {local_host}:{local_port}')
serve(app, host=local_host, port=local_port)
Note que no script acima, definimos uma nova porta e endereço de acesso.
Quando um usuário acessar o app pelo navegador a url http://0.0.0.0:8080
, ele estará se comunicando com o WSGI (Web Server Gateway Interface,waitress
) que, por sua vez, se comunica com o servidor.
Estrutura de arquivos do projeto, após a inclusão do .env
e do main.py
:
nome-do-app/
│
├── .env
│
├── static
│ ├── favicon.ico
│ ├── how_to.gif
│ ├── no.ico
│ └── yes.ico
│
├── templates/
│ ├── base.html
│ ├── index.html
│ └── how_to.html
│
├── utils/
│ └── constants.py
│
├── venv_app/
│
├── requirements.txt
├── app.py
└── main.py
Agora executamos o seguinte comando para rodar o app:
python main.py
Pronto! Basta abrir o app pelo navegador: 0.0.0.0:8080!
Este post foi a PARTE 1 do tutorial composto por duas partes. Aqui vimos como desenvolver um app que utiliza recursos de IA para a tarefa de avaliar uma suspeita de plágio entre dois textos, rodando o app localmente, em em um computador pessoal. Na PARTE 2 veremos como disponibilizar o app na Internet.
Top comments (0)