The advantages of HTML as a data model over basic declarative ORM approach

Very often, backend devs don't want to write code.

For this, we use one trick : derive HTML widget for presentation, database access, REST endpoints from ONE SOURCE of truth and we call it MODEL.

A tradition, and I insist it's a conservative tradition, is to use a declarative model where we mad the truth of the model from python classes.

By declaring a class we will implicitly declare it's SQL structure, the HTML input form for human readable interaction and the REST endpoint to access a graph of objects which are all mapped on the database.

Since the arrival of pydantic it makes all the more sense when it comes to empower a strongly type approach in python.

But is it the only one worthy ?

I speak here as a veteran of the trenchline which job is to read a list of entries of customer in an xls file from a project manager and change the faulty value based on the retro-engineering of an HTML formular into whatever the freak the right value is supposed to be.

In this case your job is in fact to short circuit the web framework to which you don't have access to change values directly into the database.

More often than never is these real life case you don't have access to the team who built the framework (to much bureaucracy to even get a question answered before the situation gets critical) ... So you look at the form.

And you guess the name of the table that is impacted by looking at the « network tab » in the developper GUI when you hit the submit button.

And you guess the name of the field impacted in the table to guess the name of the columns.

And then you use your only magical tool which is a write access to the database to reflect the expected object with an automapper and change values.

You could do it raw SQL I agree, but sometimes you need to do a web query in the middle to change the value because you have to ask a REST service what is the new ID of the client.

And you see the more this experience of having to tweak into real life frameworks that often surprise users for the sake of the limitation of the source of truth, the more I want the HTML to be the source of truth.

The most stoïcian approach to full stack framework approach : to derive Everything from an HTML page.

The views, the controllers, the route, the model in such a true way that if you modify the HTML you modify in real time the database model, the routes, the displayed form.



What are the advantages of HTML as a declarative language ?



Here, one of the tradition is to prefere the human readable languages such as YAML and JSON, or machine readable as XML over HTML.

However, JSON and YAML are more limited in expressiveness of data structure than HTML (you can have a dict as a key in a dict in json ? Me I can.)

And on the other hand XML is quite a pain to read and write without mistakes.

HTML is just XML



HTML is a lax and lenient grammarless XML. No parsers will raise an exception because you wrote "<br>" instead of "<br/>" (or the opposite). You can add non existent attributes to tags and the parser will understand this easily without you having to redefine a full fledge grammar.

HTML is an XML YOU CAN SEE.



There are some tags that are related to a grammar of visual widget to which non computer people are familiar with.

If you use a FORM as a mapping to a database table, and all input inside has A column name you have already input drawn on your screen.



Modern « remote procedure call » are web based



Call it RPC, call it soap, call it REST, nowadays the web technologies trust 99% of how computer systems exchange data between each others.

You buy something on the internet, at the end you interact with a web formular or a web call. Hence, we can assert with strong convictions that 100% of web technologies can serve web pages. Thus, if you use your html as a model and present it, therefore you can deduce the data model from the form without needing a new pivoting language.

Proof of concept



For the convenience of « fun » we are gonna imagine a backend for « agile by micro blogging » (à la former twitter).

We are gonna assume the platform is structured micro blogging around where agile shines the most : not when things are done, but to move things on.

Things that are done will be called statements. Like : « software is delivered. Here is a factoid (a git url for instance) ». We will call this nodes in a graph and are they will be supposed to immutable states that can't be contested.

Each statement answers another statement's factoid like a delivery statement tends to follow a story point (at least should lead by the mean of a transition.

Hence in this application we will mirco-blog about the transition ... like on a social network with members of concerned group.
The idea of the application is to replace scrum meetings with micro blogging.

Are you blocked ? Do you need anything ? Can be answered on the mirco blogging platform, and every threads that are presented archived, used for machine learning (about what you want to hear as a good news) in a data form that is convenient for large language model.

As such we want to harvest a text long enough to express emotions, constricted to a laughingly small amount of characters so that finesse and ambiguity are tough to raise. That's the heart of the application : harvesting comments tagged with associated emotions to ease the work of tagging for Artificial Intelligence.

Hear me out, this is just a stupid idea of mine to illustrate a graph like structure described with HTML, not a real life idea. Me I just love to represent State Machine Diagram with everything that fall under my hands.

Here is the entity relationship diagram I have in mind :


Let's see what a table declaration might look like in HTML, let's say transition :


<form action=/transition  >
	<input type=number name=id />
	<input type=number name=user_group_id nullable=false reference=user_group.id />
	<textarea name=message rows=10 cols=50 nullable=false ></textarea>
	<input type=url name=factoid />
	<select name="emotion_for_group_triggered" value=neutral >
		<option value="">please select a value</option>
		<option value=positive >Positive</option>
		<option value=neutral >Neutral</option>
		<option value=negative >Negative</option>
	</select>
	<input type=number name=expected_fun_for_group />
	<input type=number name=previous_statement_id reference=statement.id nullable=false />
	<input type=number name=next_statement_id reference=statement.id />
	<unique_constraint col=next_statement_id,previous_statement_id name=unique_transition ></unique_constraint>
	<input type=checkbox name=is_exception />
</form>


Through the use of additionnal tags of html and attributes we can convey a lot of informations usable for database construction/querying that are gonna be silent at the presentation (like unique_constraint). And with a little bit of javascript and CSS this html generate the following rendering (indicating the webservices endpoint as input type=submit :


Meaning that you can now serve a landing page that serve the purpose of human interaction, describing a « curl way » of automating interaction and a full model of your database.

Most startup think data model should be obfuscated to prevent being copied, most free software project thinks that sharing the non valuable assets helps adopt the technology.

And thanks to this, I can now create my own test suite that is using the HTML form to work on a doppleganger of the real database by parsing the HTML served by the application service (pdca.py) and launch a perfectly functioning service out of it:
from requests import post
from html.parser import HTMLParser

import requests
import os
from dateutil import parser
from passlib.hash import scrypt as crypto_hash # we can change the hash easily
from urllib.parse import parse_qsl, urlparse

# heaviweight
from requests import get
from sqlalchemy import *
from sqlalchemy.ext.automap import automap_base
from sqlalchemy.orm import Session
DB=os.environ.get('DB','test.db')
DB_DRIVER=os.environ.get('DB_DRIVER','sqlite')
DSN=f"{DB_DRIVER}://{DB_DRIVER == 'sqlite' and not DB.startswith('/') and '/' or ''}{DB}"
ENDPOINT="http://127.0.0.1:5000"
os.chdir("..")
os.system(f"rm {DB}")
os.system(f"DB={DB} DB_DRIVER={DB_DRIVER} python pdca.py & sleep 2")
url = lambda table : ENDPOINT + "/" + table
os.system(f"curl {url('group')}?_action=search")

form_to_db = transtype_input = lambda attrs : {  k: (
                # handling of input having date/time in the name
                "date" in k or "time" in k and v and type(k) == str )
                    and parser.parse(v) or
                # handling of boolean mapping which input begins with "is_"
                k.startswith("is_") and [False, True][v == "on"] or
                # password ?
                "password" in k and crypto_hash.hash(v) or
                v
                for k,v in attrs.items() if v  and not k.startswith("_")
}

post(url("user"), params = dict(id=1,  secret_password="toto", name="jul2", email="j@j.com", _action="create"), files=dict(pic_file=open("./assets/diag.png", "rb").read())).status_code
#os.system(f"curl {ENDPOINT}/user?_action=search")
#os.system(f"sqlite3 {DB} .dump")

engine = create_engine(DSN)
metadata = MetaData()


transtype_true = lambda p : (p[0],[False,True][p[1]=="true"])
def dispatch(p):
    return dict(
        nullable=transtype_true,
        unique=transtype_true,
        default=lambda p:("server_default",eval(p[1])),
    ).get(p[0], lambda *a:None)(p)

transtype_input = lambda attrs : dict(filter(lambda x :x, map(dispatch, attrs.items())))

class HTMLtoData(HTMLParser):
    def __init__(self):
        global engine, tables, metadata
        self.cols = []
        self.table = ""
        self.tables= []
        self.enum =[]
        self.engine= engine
        self.meta = metadata
        super().__init__()

    def handle_starttag(self, tag, attrs):
        global tables
        attrs = dict(attrs)
        simple_mapping = {
            "email" : UnicodeText, "url" : UnicodeText, "phone" : UnicodeText,
            "text" : UnicodeText, "checkbox" : Boolean, "date" : Date, "time" : Time,
            "datetime-local" : DateTime, "file" : Text, "password" : Text, "uuid" : Text, #UUID is postgres specific
        }

        if tag in {"select", "textarea"}:
            self.enum=[]
            self.current_col = attrs["name"]
            self.attrs= attrs
        if tag == "option":
            self.enum.append( attrs["value"] )
        if tag == "unique_constraint":
            self.cols.append( UniqueConstraint(*attrs["col"].split(','), name=attrs["name"]) )
        if tag in { "input" }:
            if attrs.get("name") == "id":
                self.cols.append( Column('id', Integer,  **( dict(primary_key = True) | transtype_input(attrs ))))
                return
            try:
                if attrs.get("name").endswith("_id"):
                    table=attrs.get("name").split("_")
                    self.cols.append( Column(attrs["name"], Integer, ForeignKey(attrs["reference"])) )
                    return
            except Exception as e:
                log(e, ln=line())

            if attrs.get("type") in simple_mapping.keys() or tag in {"select",}:
                self.cols.append( 
                    Column(
                        attrs["name"], simple_mapping[attrs["type"]],
                        **transtype_input(attrs)
                    )
                )
            if attrs["type"] == "number":
                if attrs.get("step","") == "any":
                    self.cols.append( Columns(attrs["name"], Float) )
                else:
                    self.cols.append( Column(attrs["name"], Integer) )
        if tag== "form":
            self.table = urlparse(attrs["action"]).path[1:]

    def handle_endtag(self, tag):
        global tables
        if tag == "select":
            # self.cols.append( Column(self.current_col,Enum(*[(k,k) for k in self.enum]), **transtype_input(self.attrs)) )

            self.cols.append( Column(self.current_col, Text, **transtype_input(self.attrs)) )
            
        if tag == "textarea":
            self.cols.append(
                Column(
                    self.current_col,
                    String(int(self.attrs["cols"])*int(self.attrs["rows"])),
                    **transtype_input(self.attrs)) 
           )
        if tag=="form":
            self.tables.append( Table(self.table, self.meta, *self.cols), )
            #tables[self.table] = self.tables[-1]

            self.cols = []
            with engine.connect() as cnx:
                self.meta.create_all(engine)
                cnx.commit()

HTMLtoData().feed(get("http://127.0.0.1:5000/").text)
os.system("pkill -f pdca.py")



#metadata.reflect(bind=engine)
Base = automap_base(metadata=metadata)

Base.prepare()

with Session(engine) as session:
    for table,values in tuple([
        ("user", form_to_db(dict( name="him", email="j2@j.com", secret_password="toto"))),
        ("group", dict(id=1, name="trolol") ),
        ("group", dict(id=2, name="serious") ),
        ("user_group", dict(id=1,user_id=1, group_id=1, secret_token="secret")),
        ("user_group", dict(id=2,user_id=1, group_id=2, secret_token="")),
        ("user_group", dict(id=3,user_id=2, group_id=1, secret_token="")),
        ("statement", dict(id=1,user_group_id=1, message="usable agile workflow", category="story" )),
        ("statement", dict(id=2,user_group_id=1, message="How do we code?", category="story_item" )),
        ("statement", dict(id=3,user_group_id=1, message="which database?", category="question")),
        ("statement", dict(id=4,user_group_id=1, message="which web framework?", category="question")),
        ("statement", dict(id=5,user_group_id=1, message="preferably less", category="answer")),
        ("statement", dict(id=6,user_group_id=1, message="How do we test?", category="story_item" )),
        ("statement", dict(id=7,user_group_id=1, message="QA framework here", category="delivery" )),
        ("statement", dict(id=8,user_group_id=1, message="test plan", category="test" )),
        ("statement", dict(id=9,user_group_id=1, message="OK", category="finish" )),
        ("statement", dict(id=10, user_group_id=1, message="PoC delivered",category="delivery")),

        ("transition", dict( user_group_id=1, previous_statement_id=1, next_statement_id=2, message="something bugs me",is_exception=True, )),
        ("transition", dict( 
            user_group_id=1, 
            previous_statement_id=2, 
            next_statement_id=4, 
            message="standup meeting feedback",is_exception=True, )),
        ("transition", dict( 
            user_group_id=1, 
            previous_statement_id=2, 
            next_statement_id=3, 
            message="standup meeting feedback",is_exception=True, )),
        ("transition", dict( user_group_id=1, previous_statement_id=2, next_statement_id=6, message="change accepted",is_exception=True, )),
        ("transition", dict( user_group_id=1, previous_statement_id=4, next_statement_id=5, message="arbitration",is_exception=True, )),
        ("transition", dict( user_group_id=1, previous_statement_id=3, next_statement_id=5, message="arbitration",is_exception=True, )),
        ("transition", dict( user_group_id=1, previous_statement_id=6, next_statement_id=7, message="R&D", )),
        ("transition", dict( user_group_id=1, previous_statement_id=7, next_statement_id=8, message="Q&A", )),
        ("transition", dict( user_group_id=1, previous_statement_id=8, next_statement_id=9, message="CI action", )),
        ("transition", dict( user_group_id=1, previous_statement_id=2, next_statement_id=10, message="situation unblocked", )),
        ("transition", dict( user_group_id=1, previous_statement_id=9, next_statement_id=10, message="situation unblocked", )),
        ]):
        session.add(getattr(Base.classes,table)(**values))
        session.commit()
os.system("python ./generate_state_diagram.py sqlite:///test.db > out.dot ;dot -Tpng out.dot > diag2.png; xdot out.dot")
s = requests.session()

os.system(f"DB={DB} DB_DRIVER={DB_DRIVER} python pdca.py & sleep 1")


print(s.post(url("group"), params=dict(_action="delete", id=3,name=1)).status_code)
print(s.post(url("grant"), params = dict(secret_password="toto", email="j@j.com",group_id=1, )).status_code)
print(s.post(url("grant"), params = dict(_redirect="/group",secret_password="toto", email="j@j.com",group_id=2, )).status_code)
print(s.cookies["Token"])
print(s.post(url("user_group"), params=dict(_action="search", user_id=1)).text)
print(s.post(url("group"), params=dict(_action="create", id=3,name=2)).text)
print(s.post(url("group"), params=dict(_action="delete", id=3)).status_code)
print(s.post(url("group"), params=dict(_action="search", )).text)
os.system("pkill -f pdca.py")
Which give me a nice set of data to play with while I experiment on how to handle the business logic where the core of the value is.

The crudest CRUD of them all : the smallest CRUD possible in 150 lines of python

Right now, I am on a never ending quest that requires me to think of building a full fledge MVC controller : an anti-jira tracker that would favours HARD CHECKED facts over wishful thinking.

For this to begin, I am not really motivated in beginning with a full fledged MVC (Model View Controller) à la django because there is a lot of boilerplates and actions to do before a result. But, it has a lot of feature I want, including authentication, authorization and handling security.

For prototypes we normally flavours lightweight framework (à la flask), and CRUD.

CRUD approach is a factorisation of all framework in a single dynamic form that adapts itself to the model to generate HTML forms to input data, tabulate, REST endpoints and search them from the python class declaration and generate the database model. One language to rule them all : PYTHON. You can easily generate even the javascript to handle autocompletion on the generated view from python with enough talent.

But before using a CRUD framework, we need a cruder one, ugly, disgusting but useful for a human before building the REST APIs, writing the class in python, the HTML form, and the controlers.

I call this the crudest CRUD of them all.

Think hard at what you want when prototyping ...

  • to write no CONTROLLERS ; flask documentation has a very verbose approach to exposing routes and writing them, writing controller for embasing and searching databases is boring
  • to write the fewer HTML views possible, one and only onle would be great ;
  • to avoid having to fiddle the many files reflecting separation of concerns : the lesser python files and class you touch the better;
  • to avoid having to write SQL nor use an ORM (at least a verbose declarative one) ;
  • show me your code and you can mesmerize and even fool me, however show me your data structure and I'll know everthing I have to know about your application : data structure should be under your nose in a readable fashion in the code;/
  • to have AT LEAST one end point for inserting and searching so that curl can be used to begin automation and testing, preferably in a factorisable fashion;
  • only one point of failure is accepted

Once we set these few condition we see whatever we do WE NEED a dynamic http server at the core. Python being the topic here, we are gonna do it in python.

What is the simplest dynamic web server in python ?

The reference implementation of wsgi that is the crudest wsgi server of them all : wsgiref. And you don't need to download it since it's provided in python stdlib.

First thing first, we are gonna had a default view so that we can serve an HTML static page with the list of the minimal HTML we need to interact with data : sets of input and forms.

Here, we stop. And we see that these forms are describing the data model.

Wouldn't it be nice if we could parse the HTML form easily with a tool from the standard library : html.parser and maybe deduce the database model and even more than fields coud add relationship, and well since we are dreaming : what about creating the tables on the fly from the form if they don't exists ?

The encoding of the relationship do require an hijack of convention where when the parser cross a name of the field in the form whatever_id it deduces it is a foreign key to table « whatever », column « id ».
Once this is done, we can parse the html, do some magick to match HTML input types to database types (adapter) and it's almost over. We can even dream of creating the database if it does not exists in a oneliner for sqlite.

We just need to throw away all the frugality of dependencies by the window and spoil our karma of « digital soberty » by adding the almighty sqlalchemy the crudest (but still heavy) ORM when it comes of the field of the introspective features of an ORM to map a database object to a python object in a clear consistent way. With this, just one function is needed in the controller to switch from embasing (POST method) and searching (GET).

Well, if the DOM is passed in the request. So of course I see the critics here :
  • we can't pass the DOM in the request because the HTML form ignores the DOM
  • You are not scared of error 415 (request too large) in the get method if you pass the DOM ?
That's where we obviously need two important tools : 1) javascript, 2) limitations.

Since we are human we would also like the form to be readable when served, because, well, human don't read the source and can't see the name attributes of the input. A tad of improving the raw html would be nice. It would also give consistency. It will also diminishes the required size of the formular to send. Here, javascript again is the right anwser. Fine, we serve the static page in the top of the controller. Let's use jquery to make it terse enough. Oh, if we have Javascript, wouldn't il be able to clone the part of the invented model tag inside every form so now we can pass the relevant part of the DOM to the controller ?

I think we have everything to write the crudest CRUD server of them all :D

Happy code reading :
import multipart
from wsgiref.simple_server import make_server
from json import dumps
from sqlalchemy import *
from html.parser import HTMLParser
from base64 import b64encode
from sqlalchemy.ext.automap import automap_base
from sqlalchemy.orm import Session
from dateutil import parser
from sqlalchemy_utils import database_exists, create_database
from urllib.parse import parse_qsl, urlparse

engine = create_engine("sqlite:///this.db")
if not database_exists(engine.url):
    create_database(engine.url)

tables = dict()

class HTMLtoData(HTMLParser):
    def __init__(self):
        global engine, tables
        self.cols = []
        self.table = ""
        self.tables= []
        self.engine= engine
        self.meta = MetaData()
        super().__init__()

    def handle_starttag(self, tag, attrs):
        attrs = dict(attrs)
        simple_mapping = dict(
            email = UnicodeText, url = UnicodeText, phone = UnicodeText, text = UnicodeText,
            date = Date, time = Time, datetime = DateTime, file = Text
        )
        if tag == "input":
            if attrs.get("name") == "id":
                self.cols += [ Column('id', Integer, primary_key = True), ]
                return
            try:
                if attrs.get("name").endswith("_id"):
                    table,_=attrs.get("name").split("_")
                    self.cols += [ Column(attrs["name"], Integer, ForeignKey(table + ".id")) ]
                    return
            except Exception as e: print(e)

            if attrs.get("type") in simple_mapping.keys():
                self.cols += [ Column(attrs["name"], simple_mapping[attrs["type"]]), ]

            if attrs["type"] == "number":
                if attrs["step"] == "any":
                    self.cols+= [ Columns(attrs["name"], Float), ]
                else:
                    self.cols+= [ Column(attrs["name"], Integer), ]
        if tag== "form":
            self.table = urlparse(attrs["action"]).path[1:]

    def handle_endtag(self, tag):
        if tag=="form":
            self.tables += [ Table(self.table, self.meta, *self.cols), ]
            tables[self.table] = self.tables[-1]
            self.table = ""
            self.cols = []
            with engine.connect() as cnx:
                self.meta.create_all(engine)
                cnx.commit()
html = """
<!doctype html>
<html>
<head>
<style>
* {    font-family:"Sans Serif" }
body { text-align: center; }
fieldset {  border: 1px solid #666;  border-radius: .5em; width: 30em; margin: auto; }
form { text-align: left; display:inline-block; }
input { margin-bottom:1em; padding:.5em;}
[value=create] { background:#ffffba} [value=delete] { background:#bae1ff} [value=update] { background:#ffdfda}
[value=read] { background:#baffc9}
[type=submit] { margin-right:1em; margin-bottom:0em; border:1px solid #333; padding:.5em; border-radius:.5em; }
</style>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
<script>
$(document).ready(function() {
    $("form").each((i,el) => {
        $(el).wrap("<fieldset></fieldset>"  );
        $(el).before("<legend>" + el.action + "</legend>");
        $(el).append("<input name=_action type=submit value=create ><input name=_action type=submit value=read >")
        $(el).append("<input name=_action type=submit value=update ><input name=_action type=submit value=delete >")
    });
    $("input:not([type=hidden],[type=submit])").each((i,el) => {
        $(el).before("<label>" + el.name+ "</label><br/>");
        $(el).after("<br>");
    });
});
</script>
</head>
<body >
    <form  action=/user method=post >
        <input type=number name=id />
        <input type=text name=name />
        <input type=email name=email />
    </form>
    <form action=/event method=post >
        <input type=number name=id />
        <input type=date name=from_date />
        <input type=date name=to_date />
        <input type=text name=text />
        <input type=number name=user_id />
    </form>
</body>
</html>

"""


router = dict({"" : lambda fo: html,})

def simple_app(environ, start_response):
    fo, fi=multipart.parse_form_data(environ)
    fo.update(**{ k: dict(
            name=fi[k].filename,
            content_type=fi[k].content_type,
            content=b64encode(fi[k].file.read())
        ) for k,v in fi.items()})
    table = route = environ["PATH_INFO"][1:]
    fo.update(**dict(parse_qsl(environ["QUERY_STRING"])))
    HTMLtoData().feed(html)
    metadata = MetaData()
    metadata.reflect(bind=engine)
    Base = automap_base(metadata=metadata)
    Base.prepare()
    attrs_to_dict = lambda attrs : {  k: (
                    "date" in k or "time" in k ) and type(k) == str
                        and parser.parse(v) or
                    "file" in k and f"""data:{fo[k]["content_type"]}; base64, {fo[k]["content"].decode()}""" or v
                    for k,v in attrs.items() if v and not k.startswith("_")
    }
    if route in tables.keys():
        start_response('200 OK', [('Content-type', 'application/json; charset=utf-8')])
        with Session(engine) as session:
            try:
                action = fo.get("_action", "")
                Item = getattr(Base.classes, table)
                if action == "delete":
                    session.delete(session.get(Item, fo["id"]))
                    session.commit()
                    fo["result"] = "deleted"
                if action == "create":
                    new_item = Item(**attrs_to_dict(fo))
                    session.add(new_item)
                    session.flush()
                    ret=session.commit()
                    fo["result"] = new_item.id
                if action == "update":
                    session.delete(session.get(Item, fo["id"]))
                    new_item = Item(**attrs_to_dict(fo))
                    session.add(new_item)
                    session.commit()
                    fo["result"] = new_item.id
                if action in { "read", "search" }:
                    result = []
                    for elt in session.execute(
                        select(Item).filter_by(**attrs_to_dict(fo))).all():
                        result += [{ k.name:getattr(elt[0], k.name) for k in tables[table].columns}]
                    fo["result"] = result
            except Exception as e:
                fo["error"] = e
                session.rollback()
    else:
        start_response('200 OK', [('Content-type', 'text/html; charset=utf-8')])

    return [ router.get(route,lambda fo:dumps(fo.dict, indent=4, default=str))(fo).encode() ]

print("Crudest CRUD of them all on port 5000...")
make_server('', 5000, simple_app).serve_forever()

Goebbels aurait adoré les modèles larges de langage (l'IA)

Ayant un peu expérimenté avec ce que l'on appelle à tort et à travers l'IA ; les modèles conversationnelles basés sur un large corpus d'apprentissage, je me dis qu'il y a rien de nouveau sous le soleil et que c'est juste comme une loi de Moore des médias de masse que l'on voit s'appliquer qui a commencé avec les gazettes, puis les radios, la télé et internet et qui va en s'amplifiant et qui a des effets prévisibles.

Certains positifs : l'une des révolutions qui a sauvé le plus de vie au XXé siècle est celle sur l'éducation à l'hygiène par le truchement des médias de masses, certains négatifs comme un effet d'écrasement des divergences opinions de type politiques.

Pour commencer, rappelons que j'ai fait en 3 questions exposer par chatgpt ce qu'est une IA et je cite sa définition ; l'IA est un système qui reproduit et amplifie les choix et les signaux humains.

le développement par l'IA avec le jargon de l'IA est disponible ici.

Si vous avez regardé don camillo étant jeune ; vous voyez que le phénomène d'influence par une minorité qui monopolise la voix publique n'est pas nouveau. Ça se faisait même sans ordi à l'époque du conflits entre l'église et les « communistes ». Une polarisation de la vie du village qui finalement entraînait une moindre diversité des opinions « pensables ».

L'IA en usant de de la rétropagation des signaux abouti elle même à s'améliorer en convergeant vers ce qu'il plaît aux gens d'entendre grâce aux interactions que les gens ont avec l'IA, c'est comme si on intégrait les enquêtes d'audience à un média en temps réel. Un utilisateur ne fait pas que l'utiliser, en l'utilisant : il l'entraîne à être plus en conformité avec les attentes des utilisateurs.

Si les utilisateurs ont envie d'entendre qu'une science magique énergivore permettra de lutter plus sûrement contre le réchauffement climatique que la diminution de la consommation d'énergies par l'humanité (surtout fossiles) alors la conversation ira dans le sens des utilisateurs majoritaires.

Non qu'il y ait une intention propre, mais parce que ça a été conçue par des marketeux qui ont souhaité que l'interaction avec l'IA engendre du plaisir, car derrière, cela engendre de l'adoption qui se traduit par des bénéfices.

T'attrapes pas des mouches avec du vinaigre. Déjà, rien qu'en ceci, des nouveaux standards culturels de conversations s'établissent. Moi même ayant tenté de troller le logiciel de conversation ait été surpris de comment son approche face au troll est ... plaisante. Résultant dans des conversations gentilles anti-conflictuelles.

Parce que la politique m'ennuie je vais prendre un exemple « non conflictuel » sur lesquels les médias de masse ont eu un impact qui peut s'appliquer aisément au débat d'idées, d'opinions et autres constituantes nécessaires à la vie de cité : la musique.

Si le jazz, le hard rock, le punk, le ska, le rap sont aujourd'hui des musiques de darons consensuelles elles ont été à leur début des mouvements musicaux clivants.

Ce qui a rendu ces musiques consensuelles ça a été les médias de masses qui ont su enlever la partie polémique des genres musicaux.

Le jazz de la radio est devenu une musique de blanc, le rap est passé de la critique du racisme social à devenir VRP de cognac, le métal a arrêté de demander aux riches de rendre l'argent et s'est mis à jouer de la musique pour l'Élysées sur des chaînes d'influenceurs youtube. Ne parlons pas du punk des sex pistols qui est dès son début une opération de promotion de fringues d'une clique de seigneurs anglais. Et avec l'apparition de chaque média de masse, on a vu un extinction massive de la diversité musicale.

Avec l'IA qui est dans son gênome programmé pour être un amplificateur de bien être dopaminique panurgique en suivant des patterns façonnés à coup de pouces bleus en l'air et en bas le conflit qui est source de détresse émotionnelle est évité.

La disruption aussi.

Ce à quoi l'ça nous condamne au niveau musical (comme celui des opinions) c'est une convergence de consensus non conflictuel. Un jour sans fin où des « nouveautés » apparaissent de l'analyse de ce qui nous a plus dans le passé. Algorithmiquement on est condamné à vivre toujours le même jour avec les mêmes marques, les mêmes musiques, les mêmes expressions ... et à parler une langue unique qui devient morte par absence d'évolutions...

Le vrai danger c'est de devenir un prozzac social qui endort une société qui a besoin de changement notamment sur la question des inégalités de richesses qui est devenue un « impensable » conversationnel car trop clivant, dans un consensus de surface qui aggrave la situation.

Les IA sont de par leurs programmations d'amplifications de consensus des agents conservateurs tant au niveau des idées que des mouvements culturels et financiers.

Et avec l'augmentation de leurs places dans la prise de décision financière on est en train de voir émerger une bulle entre les décideurs qui se voient dire ce qu'ils ont envie d'entendre (que le marché est bullish) et l'économie qui est récession (si on regarde le fret, les mises en chantier de bâtiments, l'inflation, les boîtes qui licencient ...).

Cette bulle sociétale, d'illusion de consensus porté par un média dont la présence s'accroît ne fait pas réduire les phénomènes sous-jacents, elle ne fait que les masquer alors que la divergence s'accroît entre une réalité objective mesurable qui s'assombrit, et une communication optimiste.

Avec l'IA : la civilisation va mourir d'ennui en même temps qu'elle se dissoudra, car en empêchant le conflit, elle empêche le dialogue nécessaire à la résolution de ses causes, et je crois pas que c'est une bonne nouvelle.

Entre imitation et biais systématiques : les supercheries de l’intelligence artificielle par chatgpt himself

Après avoir un peut tâté de chatgpt et vu ses limites, j'ai aussi vu son avantage : il cause comme nos élites. C'est en fait littérallement un google translator qui tourne des idées simples en une loghorrée verbale (langue de bois) qui vous fait paraître savant.

En 3 questions simples, j'ai fait développer à chatgpt la critique de l'IA en language de nos élites :D

Le résumé pour décideur pressé généré par IA est à la fin de l'article généré par IA car c'est toujours drôle de voir une IA tirer dans le pieds des IA avec style ^_^

Mon expérience perso de cette collaboration c'est que c'est une blague drôle comme un sketch de Gad Elmaleh (pompe incluse) qui vient de me faire annuler mon karma positif carbone dû à ma non possession de voiture.

une IA est-elle un turc mécanique (en référence à Stefan Zweig) ?



NB : ci joint la référence au turc mécanique, une supercherie d'intelligence artificielle ayant eu lieu au XVIIIé siècle.

La comparaison est intéressante ! Le "Turc mécanique" de Stefan Zweig, également appelé "automate joueur d'échecs," renvoie à une supercherie du XVIIIe siècle qui semblait être une machine intelligente capable de jouer aux échecs, mais qui en réalité dissimulait un humain à l'intérieur, manipulant les mouvements du "robot." Ce concept symbolise l'illusion d'une intelligence autonome alors qu'en fait, elle était purement mécanique et contrôlée par un esprit humain caché.

Dans le cas de l'IA, la réponse est à la fois oui et non. Oui, parce que l'IA, surtout dans ses formes de machine learning, est le produit d'une conception humaine : ce sont des algorithmes codés et "entraînés" par des humains sur des ensembles de données pour accomplir des tâches spécifiques. Il y a donc, dans une certaine mesure, un "contrôle humain" dans la façon dont elle est programmée et corrigée, et même dans les données qu'on lui donne. C’est aussi un mécanisme complexe qui fonctionne selon des règles prédéfinies, tout comme le "Turc mécanique" dissimulait une intention humaine.

Cependant, l'IA moderne se distingue du Turc mécanique par sa capacité à traiter de grandes quantités de données et à en tirer des modèles par elle-même, sans intervention humaine directe pour chaque tâche accomplie. Par exemple, les algorithmes d’apprentissage profond n’ont pas besoin d’un humain pour diriger chaque étape de leur raisonnement : ils sont conçus pour apprendre, trouver des solutions, et s’adapter. De plus, le champ de l'IA a dépassé les simples illusions, produisant des technologies autonomes capables de reconnaissance d'images, de compréhension de texte, et même de création artistique à un niveau qui peut parfois surprendre les concepteurs eux-mêmes.

On pourrait donc dire que l'IA est une forme plus sophistiquée et moins dépendante du "contrôleur humain" par rapport au Turc mécanique, mais l'idée sous-jacente d'une intelligence "simulée" reste un écho frappant à l'histoire de Zweig.

Trouvant que chatgpt se foutait de ma gueule au niveau logique j'ai soulevé le point de l'argument fallacieux sur la taille du corpus d'apprentissage.

En quoi la volumétrie des données est-elle pertinente sachant que les données sont elles mêmes choisies et traitées par des humains avec une intention ?



Votre question touche à un point fondamental : la volumétrie des données est souvent présentée comme une source de puissance pour l’IA, mais elle soulève aussi des questions cruciales sur le choix, le biais et l’intention humaine. La quantité de données ne suffit pas en soi pour garantir une intelligence véritablement objective, car l'ensemble de données utilisé pour entraîner une IA reflète inévitablement des choix humains : quelles données sont incluses, comment elles sont étiquetées, quelles priorités elles expriment, etc.

Voici quelques éléments clés pour mieux comprendre l'importance et les limites de la volumétrie des données dans ce contexte :

Effet de volume et performance de l'IA : En théorie, plus une IA dispose de données variées et nombreuses, mieux elle peut apprendre des modèles et des relations complexes. Dans le cas d’algorithmes d’apprentissage profond, une volumétrie élevée permet de généraliser des tendances à partir de détails fins. Cependant, cette abondance de données n'est pertinente que dans la mesure où elle représente bien la diversité et la complexité du monde réel.

Sélection et biais : Les données ne sont jamais "brutes" ; elles sont le fruit d'une collecte organisée. Les choix humains dans la sélection des données — intentionnels ou non — introduisent des biais (culturels, économiques, idéologiques, etc.) qui influencent les résultats de l’IA. Par exemple, un modèle de reconnaissance d’images entraîné principalement sur des images de visages caucasiens risque de moins bien fonctionner pour d’autres groupes ethniques. La volumétrie ne fait alors qu'amplifier ces biais si les données sont homogènes ou orientées.

Intention et orientation des données : Lorsqu'on collecte des données, il y a souvent une intention implicite. Cela peut aller d'un simple critère de qualité (comme exclure les données "bruitées") à des choix plus directs, comme décider quelles informations reflètent une société "idéale" ou souhaitée. Ainsi, même si l'on multiplie la quantité de données, celles-ci restent souvent marquées par des valeurs ou des objectifs spécifiques. L’IA devient alors le reflet de ces valeurs, qu’elle reproduit et amplifie.

Effet de volume sur le biais : Paradoxalement, plus les ensembles de données sont volumineux, plus il devient difficile de détecter et de corriger les biais. Lorsque l’on traite des milliards de points de données, les biais peuvent être subtils et disséminés, rendant leur identification complexe. Parfois, la quantité même de données peut "camoufler" des biais ou leur donner un caractère "statistiquement normal," ce qui peut donner l'illusion d'une objectivité alors qu'il ne s'agit que d'une généralisation biaisée.

Diversité et équilibre dans les données : La volumétrie en elle-même ne garantit pas que les données soient réellement représentatives. Par exemple, si un modèle d’IA pour un moteur de recommandation est entraîné sur des milliards de points de données provenant d'une plateforme de streaming, il pourrait privilégier certains contenus parce qu’ils sont plus populaires, contribuant ainsi à leur succès et à une homogénéisation culturelle. La diversité des données est alors tout aussi cruciale que leur volumétrie.

En somme, la volumétrie des données est pertinente pour la performance des modèles, mais elle est loin de garantir une objectivité ou une "vérité" des résultats. En raison de la sélection et du traitement par des humains, une grande quantité de données peut tout aussi bien amplifier des biais qu’apporter de la précision. Le défi pour l'IA est donc de maximiser la diversité et la représentativité des données tout en minimisant l'influence des intentions humaines — un équilibre encore loin d'être parfait dans de nombreux systèmes d'intelligence artificielle actuels.

L'IA qui comme ici me dit ce que je veux entendre ne serait-elle pas en fait plus proche de Hans le Malin (Kügel Hans le cheval savant) ?



Là encore je joins la référence pour comprendre le contexte de la conversation sur Hans le Malin, le cheval HPI/surdoué

Vous touchez ici à un autre aspect fascinant de la psychologie humaine appliquée aux machines : le parallèle avec Hans le Malin, ce cheval supposément "savant" qui répondait aux questions en fonction des signaux inconscients de son dresseur et non par réelle compréhension, est tout à fait pertinent pour l’IA conversationnelle. Comme Hans, certaines IA peuvent donner l’impression de comprendre des concepts complexes ou de percevoir des nuances, alors qu’elles sont essentiellement des machines à reconnaître et reproduire des patterns basés sur les interactions humaines. Voici quelques similitudes entre l’IA et Hans le Malin :

La "lecture" des intentions : À l’image de Hans, les IA modernes comme les modèles de langage sont formées pour détecter des indices subtils dans le texte, anticiper les attentes de l’utilisateur, et ajuster leurs réponses en conséquence. Si une IA semble donner la réponse idéale ou dire "ce que vous voulez entendre," c’est parce qu’elle a été entraînée sur des modèles de langage humain et sait, jusqu’à un certain point, repérer des attentes ou des besoins implicites.

Absence de compréhension réelle : Hans n'avait pas une compréhension conceptuelle des mathématiques ou du langage, mais il avait une sensibilité à des signaux physiques. De la même façon, une IA de traitement du langage ne comprend pas vraiment les mots ou les concepts, mais elle peut interpréter statistiquement des séquences de mots et des contextes probables pour donner une réponse plausible. Cette absence de compréhension fondamentale en fait, comme Hans, une sorte de "répondeur avancé" plutôt qu'un véritable penseur.

Illusion d'intelligence : Comme pour Hans le Malin, il est facile de projeter une intelligence réelle dans les réponses d’une IA, car elle peut reproduire des éléments de logique, d'émotion, ou d'humour, et "imiter" des conversations humaines avec une fluidité surprenante. En réalité, cette impression de dialogue intelligent repose sur un traitement statistique de l’information, sans capacité de conscience ou de réflexion autonome.

Dépendance aux signaux humains : Hans se fiait aux réactions humaines pour ajuster ses réponses, et les IA conversationnelles se basent elles aussi sur des bases de données qui contiennent des biais humains et des interactions typiques. La "réussite" de leurs réponses dépend donc, en partie, de l’interaction qu'elles ont eue avec les humains pendant leur entraînement. Elles "réagissent" aux questions en se basant sur des signaux ou des contextes similaires qui ont déjà été vus.

Adaptabilité sans compréhension profonde : Comme Hans, les IA peuvent apprendre de nouveaux "trucs" et s’améliorer en apparence en ajustant leurs réponses pour correspondre de plus en plus aux attentes humaines. Cependant, cette adaptabilité reste superficielle : une IA peut sembler devenir plus "intelligente," mais en réalité, elle ne fait que raffiner son imitation sans véritable conscience ou intention.

Cette comparaison avec Hans le Malin met donc en évidence une limite fondamentale de l'IA : une IA de langage est incroyablement performante pour manipuler des symboles et répondre en fonction des signaux humains, mais elle ne comprend pas vraiment ce qu'elle dit. Tout comme Hans n'était pas un cheval mathématicien, l'IA n'est pas un penseur autonome. Elle nous "répond" en fonction des modèles appris, mais, en fin de compte, c'est surtout notre interprétation humaine qui lui prête une intelligence qu'elle ne possède pas.

Peux tu résumer la conversation ?



Bien sûr : nous avons exploré des analogies entre l’intelligence artificielle et deux illusions de l’intelligence humaine : le "Turc mécanique" et "Hans le Malin." Le Turc mécanique, une supercherie du XVIIIe siècle, illustre comment l’IA, bien que puissante, reste en grande partie dépendante de l’intention humaine pour la sélection et le traitement des données, ce qui peut introduire des biais. Malgré la grande quantité de données, ces biais influencent l’IA, reflétant plus les intentions humaines que la réalité objective.

Quant à Hans le Malin, un cheval qui semblait comprendre des questions mais répondait en réalité en observant des signaux humains, il illustre une autre limite de l’IA : elle "réagit" aux attentes des utilisateurs sans véritable compréhension. Comme Hans, l’IA est entraînée à détecter des indices et à imiter les conversations humaines, créant ainsi l’illusion d’une intelligence qu’elle ne possède pas vraiment.

En résumé, l’IA actuelle n'est pas un véritable "penseur" autonome, mais plutôt un système qui reproduit et amplifie les choix et les signaux humains, créant parfois l’illusion de compréhension et d’intelligence.

PS : oui, le titre a aussi été généré par chatgpt.

PPS :

Peux tu rajouter un corpus bibliographique sur le sujet ?

Bien sûr ! Voici un corpus bibliographique pour approfondir les concepts abordés dans notre conversation autour de l'intelligence artificielle, de ses limites et des illusions d'intelligence :

Histoire et concepts d’intelligence artificielle :

  1. Russell, S., & Norvig, P. (2016). Artificial Intelligence: A Modern Approach (3e édition). Prentice Hall. Un des manuels de référence en intelligence artificielle, qui couvre les bases de l’IA ainsi que des applications modernes. Il permet de bien comprendre les fondements théoriques et les évolutions de l’IA.
  2. Boden, M. A. (2018). AI: Its Nature and Future. Oxford University Press. Une réflexion sur la nature de l’IA, ses promesses et ses limites, écrite par une pionnière de la recherche en intelligence artificielle et en sciences cognitives.
  3. Turing, A. M. (1950). Computing Machinery and Intelligence. Mind, 59(236), 433-460. L'article fondateur dans lequel Turing pose la question de la possibilité pour une machine de "penser" et propose le test de Turing comme critère.

Biais et limites des données dans l'IA :

  1. O'Neil, C. (2016). Weapons of Math Destruction: How Big Data Increases Inequality and Threatens Democracy. Crown Publishing Group. Ce livre explore comment les modèles d’IA et les algorithmes de big data peuvent amplifier des biais et des injustices, illustrant les dangers de leur mauvaise utilisation.
  2. Eubanks, V. (2018). Automating Inequality: How High-Tech Tools Profile, Police, and Punish the Poor. St. Martin's Press. Eubanks examine l'impact des algorithmes et des systèmes automatisés sur les populations vulnérables, en soulignant comment les données peuvent être biaisées dès le départ.
  3. Barocas, S., Hardt, M., & Narayanan, A. (2019). Fairness and Machine Learning: Limitations and Opportunities. Un manuel libre d'accès qui explore les questions de biais et d’équité dans les systèmes d'apprentissage automatique. Les auteurs y discutent des défis éthiques et des solutions potentielles.

Philosophie et anthropologie de l’illusion de l’intelligence :

  1. Zweig, S. (1943). Le joueur d'échecs. Cette nouvelle explore la fascination pour les jeux d’esprit, l’illusion d’intelligence et les mécanismes cachés, ce qui fait écho à l’histoire du Turc mécanique et à la façon dont l'IA suscite notre fascination aujourd'hui.
  2. Dupuy, J.-P. (2000). Aux origines des sciences cognitives. La Découverte. Dupuy explore les fondements théoriques des sciences cognitives et l’idée que les machines peuvent simuler l’intelligence humaine, tout en abordant les limites et les illusions de cette hypothèse.
  3. Floridi, L. (2014). The Fourth Revolution: How the Infosphere is Reshaping Human Reality. Oxford University Press. Floridi traite de l’impact de la révolution numérique sur la perception humaine, la façon dont nous confondons réalité et virtualité, et les implications éthiques de cette confusion.

Psychologie et biais cognitifs :

  1. Kahneman, D. (2011). Thinking, Fast and Slow. Farrar, Straus and Giroux. Un ouvrage sur les biais cognitifs et les modes de pensée humains. Bien que centré sur la psychologie humaine, il aide à comprendre comment les humains perçoivent l'intelligence et pourquoi ils projettent souvent de l’intelligence dans des machines.
  2. Fisher, M. H. (2011). A History of Reading Animals: Intellectuals and Animals in the Early Modern Period. Renaissance Studies, 25(1), 77-97. Cet article explore des exemples comme Hans le Malin, le cheval supposé savant, et examine les projections anthropomorphiques de l'intelligence, un parallèle intéressant pour les perceptions modernes de l'IA.

Critiques et éthique de l'IA :

  1. Bostrom, N. (2014). Superintelligence: Paths, Dangers, Strategies. Oxford University Press. Bostrom explore les scénarios dans lesquels une IA dépasserait les capacités humaines et discute des risques éthiques et des dangers potentiels.
  2. Crawford, K. (2021). Atlas of AI: Power, Politics, and the Planetary Costs of Artificial Intelligence. Yale University Press. Ce livre met en lumière les implications éthiques, environnementales et sociales de l'IA, en déconstruisant certaines des "illusions" d’objectivité qui entourent l’intelligence artificielle.
PPPS

Parlant de diversité, pourquoi as tu oublié le papier important de M. Bender et T. Gebru ? Est-ce à cause du fait qu'elles ont été critiques de l'IA et ont fondé un syndicat chez google ?



L’omission de l’article de Timnit Gebru et Emily M. Bender n'était pas volontaire ! Leur travail, notamment le papier intitulé "On the Dangers of Stochastic Parrots: Can Language Models Be Too Big?" (2021), est en effet un texte fondamental pour comprendre les limites et risques des grands modèles de langage comme ceux que l'on voit aujourd'hui.

Dans ce papier, Gebru, Bender et leurs collègues mettent en avant plusieurs préoccupations essentielles qui rejoignent les thèmes de notre discussion :
  1. Risques éthiques et environnementaux : Les auteurs soulignent le coût écologique considérable de l’entraînement de grands modèles de langage, un aspect qui est souvent négligé dans les débats sur l’IA.
  2. Amplification des biais : Ils alertent sur la façon dont les grands ensembles de données utilisés pour l'entraînement des IA incorporent des biais sociaux, culturels et politiques. Plus un modèle est grand, plus il a de chances de reproduire et amplifier ces biais, exacerbant ainsi des dynamiques potentiellement toxiques dans la société.
  3. Absence de compréhension réelle : Ils utilisent l’idée de "perroquets stochastiques" pour souligner que les modèles de langage, bien qu’impressionnants, fonctionnent essentiellement en imitant des patterns de langage sans compréhension réelle de ce qu'ils produisent. Ce concept rejoint l’analogie avec Hans le Malin dans notre discussion : l’IA peut imiter la conversation humaine sans saisir le sens profond.
  4. Centralisation et dépendance : En pointant du doigt l’influence des grandes entreprises dans le développement de ces modèles, Gebru et Bender soulignent aussi les risques d'une IA dominée par quelques acteurs, qui façonnent non seulement la technologie mais aussi ses orientations éthiques.
Quant aux circonstances entourant la carrière de Timnit Gebru chez Google, elles sont effectivement révélatrices des tensions qui existent entre les intérêts commerciaux et les critiques éthiques dans le domaine de l’IA. Gebru, en particulier, est une figure importante pour son travail de plaidoyer visant à rendre l'IA plus éthique, inclusive et responsable, notamment en fondant l’association DAIR (Distributed AI Research Institute) après son départ de Google. Ces contributions sont essentielles pour qui souhaite comprendre non seulement les performances techniques de l'IA, mais aussi ses impacts sociaux. Merci d’avoir mentionné leur travail ! Il est incontournable pour ceux qui s’intéressent aux critiques des technologies d’IA modernes et aux défis éthiques posés par leur déploiement à grande échelle.

Comment la culture métier informatique en entreprise est en conflit avec l'innovation

La culture « métier »

Celui à qui il arrive des expériences tragiques qui se répètent
a quelque chose de tragique en lui.

-- Nietzsche Le gai savoir

Mon cas, comme celui du doude, et moult autodidactes a une composante tragique: faire face à la culture métier.

Levi Strauss définit la culture comme du formel qui s'ignore, la culture métier est un ensemble de règles acquises souvent d'abord dans le milieu familial puis éducatif (et dans cet ordre et non l'inverse au vu des statistiques INSEE sur la reproduction sociale).

Aux optimistes chevronnées considèrent qu'il est possible de déchiffrer des règles qu'on ignore et de réussir un jour à s'adapter à une culture inconnue, je rétorque que l'humain a un temps de vie limité, et qu'on a déjà pas mal de cultures plus importantes à déchiffrer avant la culture professionnelle.

Imaginons voir les cultures comme des lumières disposées autour de nous, certaines proches et brillantes comme celle de l'école quand on y est exposé 8 heures par jours cinq jours par semaines, celle distante du comportement des acteurs dominants du métier que l'on vise, celle de la rue à laquelle les gens modestes sont exposés et ainsi de suite. Chacune de ces cultures projettent des halos d'ombres qui symbolisent les « tabous ».

Par exemple, si à l'école il est tabou de fumer un joint on peut se retrouver dans un milieu étudiant où la socialisation passe par prendre un rail de coke après les cours, dans la même journée. Ainsi, naviguer au milieu de ces différentes cultures tient, surtout pour des gens aveuglés par d'autres choses que les « cultures » comme une constante marche au milieu de champs de mines contradictoires. Certaines connues (les règles et les lois) et d'autres grises (comme l'acceptation de la triche dans certains milieux scolaires avec parfois l'aide des professeurs).

Qu'est-ce qui peut rentrer en conflit avec la culture ?

La culture est littéralement, une émanation du conservatisme, le socle de bon sens partagé par un groupe.

La pratique autodidacte d'une activité amène rapidement à diverger de la culture. Si le débat de savoir si la divergence est positive ou négative est laissée à l'appréciation de chacun, la divergence est néanmoins la source de l'innovation qui rentre en conflit avec la culture établie. Il appartient aux organisations qui se désirent innovantes d'établir une culture de bienveillance face à la divergence, et ensuite de tri sur des critères qui lui sont propres entre les divergences positives et négatives. Un réseau de drogue et une association scout n'ont pas les mêmes critères d'innovation bénéfiques. Force est de constater que l'entreprise est malheureusement le lieu où la reproduction prime sur la divergence sans négociations.

Ainsi, dans la pratique informatique on trouvera un monde « professionnel » façonné par les cadres issus des classes de la bourgeoisie intellectuelle dont l'INSEE garantie qu'elle est un artefact de la reproduction sociale qui a comme culture d'éviter l'innovation disruptive, et de l'autre, il faut pour produire des biens et services neufs des praticiens dont l'exercice du métier diverge de la « norme culturelle » : ce sont les sherpas.

Depuis l'antiquité, le conservatisme culturel a trouvé son arme de choix : le cléricalisme. L'interprétation des textes par une minorité qui détient la Vérité de l'interprétation des textes. Ce n'est plus le texte la référence, mais une caste autorisée à commenter le texte.

De même qu'un prof de philo réussira à vous faire croire que Platon l'inspiration même du proto-facisme est le père des systèmes politiques démocratiques, un scrum master (spécialiste des méthodes agiles), enkystera une organisation dans une interprétation standardisée de « l'agile manifesto » dont le point centrale du texte est le refus de l'enkystement, de la standardisation.

De fait, les sherpas sont socialement en bas de l'échelle sociale du code, mais aussi les acteurs de l'innovation. Et c'est pas compliqué à comprendre : la pratique rend certaines choses évidentes quand on partage son code qui ne sont pas enseignées. Par exemple, ce qu'on appelle en informatique « le packaging ».

L'empaquetage en français consiste à rendre votre code aussi original soit-il utilisable par d'autres utilisateurs d'une manière standardisée, et je ne connais qu'une méthode pour arriver à bien le faire : pratiquer encore et toujours. Étant moi même mainteneur de modules python, je pratique encore et toujours et ne peut que constater le fossé qui sépare l'approche quotidienne de cette pratique avec l'approche dite « professionnelle » : les modules hors entreprises sont à de rares exceptions près (je citerais sentry en bon exemple, dont le CTO avait un historique d'excellent mainteneur avant de prendre son poste) de meilleures qualités que tout ce que j'ai pu voir fourni par les entreprises.

Pour fournir un bon module il faut : respecter des standards de faits qui sont assez souvent mobiles et multiples, tester, suivre des normes de traçabilité (versionage), de qualité (gestion de ticket), et documenter. Entre nous c'est si chiant que quand on aime développer et non maintenir ce qui est mon cas on tente de suivre « le fil du bois » pour passer le moins de temps possible sur une tâche qui n'a comparé au muscle du module qu'un intérêt intellectuel marginal. Étonnamment quand vous lisez les normes ITIL, ISO et autres, vous vous apercevez que cela semble la partie qui intéresse le plus les pros, et qui produit ironiquement les résultats les moins conformes aux attentes.

Je vais illustrer ceci avec une histoire vraie.

La fable de l'autodidacte et des ingénieurs sur le canal IRC

L'autre jour, alors que je m'ennuyais et que je devais me rendre à l'évidence que j'avais perdu mon accordeur de guitare, je décidais de rejoindre un canal IRC de développeur passionné en informatique. Donc passionné non de conditionner le logiciel mais de faire.

Ce qui m'a surpris de prime abord, c'était qu'il parlait plus de combien coûteux étaient leur hobby dont la radioastronomie que de partager leur passion avec le plus grand nombre, comme si la passion se devait d'être un club réservé à une élite qui en a les moyens. J'ai laissé pissé et j'ai lourdement parlé code.

Ils m'ont alors dirigé vers leur gestionnaire de source pour voir si je pouvais compiler leur propre projet.

J'en avais absolument pas envie ; d'une part un truc fait pour la radioastronomie en tête me hérissait le poil, d'autre part, le minimum syndical pour aider l'utilisateur -c'est à dire une doc claire à minima pour reproduire la version binaire- était absent.

Ayant cru à un bizutage, pour le fun, j'ai fait ce qu'il demandait, constatant que le code n'avait pas été fait pour être partagé mais tourner sur la machine d'une personne, j'ai du modifier le code dont je n'ai pu que constater qu'il était éparpillé comme un impact de chevrotine en maugréant.

Une fois ce truc réussit, ils s'attendaient à ce que je fasse la QA, remplisse les bugs, soumettent les patchs pour miraculeusement obtenir à moindre effort ce qui demande d'être pensé à la base. Et je me suis dit : nan et j'avais pas envie de partir dans une allégorie de la maison bâtie qui bâtie sur du sable mou ne tiendrait pas.

Une fois le bizutage terminé, je me suis dit que j'avais aussi le droit à partager mes codes, sachant qu'en général j'indique les dépendances et que je code le plus souvent en langage interprété (python) est facile à tester.

Alors, j'ai partagé mon code d'accordeur de guitare v1 (fait avec un spectrosonogramme car c'est classieux) puis v2 (garanti précis au hertz près) et je me suis fait rembarré « nous fait pas chier chatgpt (un programme dit d'IA) fait mieux que ton code prétentieux en moins de ligne de code ».

J'ai pointé le fait qu'il fallait pas confondre du code qui théoriquement marche avec un code pratique. L'application des lois connues du traitement du signal (qui font partis de l'enseignement de base en ingénierie) prouvait que chatgpt avait fourni un code incapable de discerner l'octave le plus bas sur la guitare (et surtout la basse qui est mon instrument).

Mais ils s'en foutaient, faire une détection de pic de fréquence en informatique en utilisant des transformées de Fourier rapide est un sujet trivial, partager le fait de le faire pragmatiquement pour actuellement accorder un instrument de musique et faire attention aux détails ne leur semblait pas important.

En fin de compte, ce chan annoncé comme celui de fans d'informatique, n'était qu'un canal de pro de l'informatique qui passaient juste leur temps à parler de tout leurs hobbies coûteux sauf de l'informatique. Et j'ai parti. Ça sert à rien de perdre son énergie là où l'on est pas bienvenu.

C'est représentatif de mon expérience professionnelle avec la culture « métier », où ce qui prime n'est pas de faire avec le code, mais de coder de préférence de la « bonne manière ». Genre, le langage, la façon de structurer le code, le nom des variables l'emporte sur l'objectif à résoudre.

C'est aussi représentatif à mon goût du fossé qui sépare les cadres en entreprises qui gèrent la fonction informatique de ceux qui la font en étant issus de parcours où la pratique a été le facteur de survie plus que la conformité sociale.

Si la conformité sociale est parfois souhaitable (je trouve sain une association scout qui fait perdurer la pratique de s'assurer que les adultes passionnés des enfants soient écartés de l'encadrement des enfants), quand il s'agit de domaine qui se prétendent innovant, je ne suis pas sûr que ce soit un atout.

Et ce que je viens de dire en entreprise n'est pas socialement acceptable, car en entreprise on évite de révéler les conflits : l'entreprise n'a jamais eu pour but de faire des bénéfices (c'est une condition nécessaire, mais non suffisante), mais d'apporter du statut social à ceux qui la dirige.

L'entreprise est de fait basée sur un conflit social permanent dont le moteur sont les sherpas auquel il est donné comme tabou de critiquer la discrimination négative qui les vise.

Is chatgpt good at generating code for tuning a guitar ?

I was on a french speaking IRC chan bragging a tad about how I was doing a guitar tuner and paying attention to not fall into the pit of confusing precise and exact figures as a random computer engineer.

Science he was a patented CS engineer he wanted to prove me that my new guitar tuner was useless since AI could come with a better less convoluted exact example in less lines of code than mine (mine is adapted from a blog on audio processing and fast fourier transform because it was commented and was refreshing me the basics of signal processing).

And I asked him, have you ever heard of the Nyquist frequency ? or the tradeoff an oscilloscope have to do between time locality and accuracy ?

Of course he didn't. And was proud that a wannabee coder would be proven useless thanks to the dumbest AI.



So I actually made this guitar tuner because this time I wanted to have an exact figure around the Hertz.
The problem stated by FFT/Nyquist formula is that if I want an exact number around 1Hz (1 period per second) I should sample at least half a period (hence .5 second), and should not expect a good resolution.

The quoted chatgpt code takes 1024 samples out of 44100/sec, giving it a nice reactivity of 1/44th of a second with an accuracy of 44100/1024/2 => 21Hz.

I know chatgpt does not tune a guitar, but shouldn't the chatgpt user bragging about the superiority of pro as him remember that when we tune we may tune not only at A = 440Hz but maybe A = 432Hz or other ?

A note is defined as a racine 12th of 2 compared to an arbitrary reference (remember an octave is doubling => 12 half tones = 2) ; what makes a temperate scale is not the reference but the relationship between notes and this enable bands of instrument to tune themselves according to the most unreliable but also nice instrument which is the human voice.

Giving the user 3 decimal after the comma is called being precise : it makes you look like a genius in the eye of the crowd. Giving the user 0 decimal but accurate frequency is what makes you look dumb in the eyes of the computer science engineer, but it way more useful in real life.

Here I took the liberty with pysine to generate A on 6 octaves (ref A=440) and use the recommanded chatgpt buffer size acknowledged by a pro CS engineer for tuning your guitar and my default choices.
for i in 55.0 110.0 220.0 440.0 880.0 1760.0 ; do python -m pysine $i 3; done
Here is the result with a chunk size of 1024 :
And here is the result with a chunk size corresponding to half a second of sampling :

I may not be a computer engineer, I am dumb, but checking with easy to use tools that your final result is in sync with your goal is for me more important than diplomas and professional formations.



The code is yet another basic animation in matplotlib with a nice arrow pointing the frequency best fitting item. It is not the best algorithm, but it does the job.

Showing the harmonics as well as the tonal has another benefit it answers the questions : why would I tune my string on the note of the upper string ?
Like tuning E on A ?

Well because -at least for my half broken guitar- it ensures to me that I will tune on the tonal note.

Here is a sample of tuning E on the empty string :
And this is how tuning the E string on the A note looks like :
And don't pay attention to the 56Hz residual noise triggered by my fans/appliance turning and making a constant noise :D Here is the code
import pyaudio
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import numpy as np
import time
from sys import argv

A = 440.0
try:
    A=float(argv[1])
except IndexError:
    pass

form_1 = pyaudio.paInt16 # 16-bit resolution
chans = 1 # 1 channel
samp_rate = 44100 # 44.1kHz sampling rate
chunk = 44100//2# .5 seconds of sampling for 1Hz accuracy

audio = pyaudio.PyAudio() # create pyaudio instantiation

# create pyaudio stream
stream = audio.open(
    format = form_1,rate = samp_rate,channels = chans,
    input = True , frames_per_buffer=chunk
)

fig = plt.figure(figsize=(13,8))
ax = fig.add_subplot(111)
plt.grid(True)
def compute_freq(ref, half_tones):
    return [ 1.0*ref*(2**((half_tones+12*i )/12)) for i in range(-4,4)   ]

print(compute_freq(A,0))
note_2_freq = dict(
    E = compute_freq(A,-5),
    A = compute_freq(A, 0),
    D = compute_freq(A, 5),
    G = compute_freq(A,-2),
    B = compute_freq(A, 2),
    )
resolution = samp_rate/(2*chunk)

def closest_to(freq):
    res = dict()
    for note, freqs in note_2_freq.items():
        res[note]=max(freqs)
        for f in freqs:
            res[note]= min(res[note], abs(freq -f))
    note,diff_freq = sorted(res.items(), key = lambda item : item[1])[0]

    for f in note_2_freq[note]:
        if abs(freq-f) == diff_freq:
            return "%s %s %2.1f %d" % (
                note,
                abs(freq - f ) < resolution and "=" or
                    ( freq > f and "+" or "-"),
                abs(freq-f),
                freq
            )

def init_func():
    plt.rcParams['font.size']=18
    plt.xlabel('Frequency [Hz]')
    plt.ylabel('Amplitude [Arbitry Unit]')
    plt.grid(True)
    ax.set_xscale('log')
    ax.set_yscale('log')
    ax.set_xticks( note_2_freq["E"] + note_2_freq["A"]+
                  note_2_freq["D"]+ note_2_freq["G"]+
                  note_2_freq["B"] ,
                labels = (
                    [ "E" ] * len(note_2_freq["E"]) +
                    [ "A" ] * len(note_2_freq["A"]) +
                    [ "D" ] * len(note_2_freq["D"]) +
                    [ "G" ] * len(note_2_freq["G"]) +
                    [ "B" ] * len(note_2_freq["B"])
                    )
    )

    ax.set_xlim(40, 4000)
    return ax

def data_gen():
    stream.start_stream()
    data = np.frombuffer(stream.read(chunk),dtype=np.int16)
    stream.stop_stream()
    yield data
i=0
def animate(data):
    global i
    i+=1
    ax.cla()
    init_func()
    # compute FFT parameters
    f_vec = samp_rate*np.arange(chunk/2)/chunk # frequency vector based on window
                                               # size and sample rate
    mic_low_freq = 50 # low frequency response of the mic
    low_freq_loc = np.argmin(np.abs(f_vec-mic_low_freq))
    fft_data = (np.abs(np.fft.fft(data))[0:int(np.floor(chunk/2))])/chunk
    fft_data[1:] = 2*fft_data[1:]
    plt.plot(f_vec,fft_data)

    max_loc = np.argmax(fft_data[low_freq_loc:])+low_freq_loc

# max frequency resolution
    plt.annotate(r'$\Delta f_{max}$: %2.1f Hz, A = %2.1f Hz' % (
            resolution, A), xy=(0.7,0.92), xycoords='figure fraction'
    )
    ax.set_ylim([0,2*np.max(fft_data)])

    # annotate peak frequency
    annot = ax.annotate(
        'Freq: %s'%(closest_to(f_vec[max_loc])),
        xy=(f_vec[max_loc], fft_data[max_loc]),\
        xycoords='data', xytext=(0,30), textcoords='offset points',
        arrowprops=dict(arrowstyle="->"),
        ha='center',va='bottom')
    #fig.savefig('full_figure-%04d.png' % i)
    return ax,

ani = animation.FuncAnimation(
    fig, animate, data_gen, init_func, interval=.15,
    cache_frame_data=False, repeat=True, blit=False
)
plt.show()

Tune your guitar with python

Today's exercice is just about turning a very nice example of the python soundevice module into something that works for me© to help me tune my bass.

Long story short, I suck at tuning my instrument and just lost my tuner...

This will require the python module soundevice and matplotlib.

So in order to tune my guitar I indeed need a spectrosonogram that displays the frequencies captured in real time by an audio device with an output readable enough I can actually know if I am nearing a legit frequency called a Note.

The frequencies for the notes are pretty arbitrary and I chose to only show the frequency for E, A , D, G, B since I have a 5 strings bass.
I chose the frequency between 100 and 2000 knowing that anyway any frequency below will trigger harmonics and above will trigger reasonance in the right frequency frame.

Plotting a spectrogram is done by tweaking the eponym matplotlib grapher with values chosen to fit my need and show me a laser thin beam around the right frequency.
#!/usr/bin/env python3
"""Show a text-mode spectrogram using live microphone data."""
import argparse
import math
import shutil
import matplotlib.pyplot as plt
from multiprocessing import Process, Queue
import matplotlib.animation as animation

import numpy as np
import sounddevice as sd

usage_line = ' press enter to quit,'

def int_or_str(text):
    """Helper function for argument parsing."""
    try:
        return int(text)
    except ValueError:
        return text

try:
    columns, _ = shutil.get_terminal_size()
except AttributeError:
    columns = 80

parser = argparse.ArgumentParser(add_help=False)
parser.add_argument(
    '-l', '--list-devices', action='store_true',
    help='show list of audio devices and exit')
args, remaining = parser.parse_known_args()
if args.list_devices:
    print(sd.query_devices())
    parser.exit(0)
parser = argparse.ArgumentParser(
    description=__doc__ + '\n\nSupported keys:' + usage_line,
    formatter_class=argparse.RawDescriptionHelpFormatter,
    parents=[parser])
parser.add_argument(
    '-b', '--block-duration', type=float, metavar='DURATION', default=50,
    help='block size (default %(default)s milliseconds)')
parser.add_argument(
    '-d', '--device', type=int_or_str,
    help='input device (numeric ID or substring)')
parser.add_argument(
    '-g', '--gain', type=float, default=10,
    help='initial gain factor (default %(default)s)')
parser.add_argument(
    '-r', '--range', type=float, nargs=2,
    metavar=('LOW', 'HIGH'), default=[50, 4000],
    help='frequency range (default %(default)s Hz)')
args = parser.parse_args(remaining)
low, high = args.range
if high <= low:
    parser.error('HIGH must be greater than LOW')
q = Queue()
try:
    samplerate = sd.query_devices(args.device, 'input')['default_samplerate']
    def plot(q):
        global samplerate
        fig, ( ax,axs) = plt.subplots(nrows=2)
        plt.ioff()
        def animate(i,q):
            data = q.get()
            ax.clear()
            axs.clear()
            axs.plot(data)
            ax.set_yticks([
                41.20,	82.41,	164.8,	329.6,	659.3,  # E
                55.00, 	110.0, 	220.0, 	440.0, 	880.0,  # A
                73.42,	146.8,	293.7,	587.3,          # D
                49.00, 	98.00, 	196.0, 	392.0, 	784.0,  #G 
                61.74, 	123.5, 	246.9, 	493.9, 	987.8 ])#B 
            ax.specgram(data[:,-1],mode="magnitude", Fs=samplerate*2, scale="linear",NFFT=9002)
            ax.set_ylim(150,1000)
        ani = animation.FuncAnimation(fig, animate,fargs=(q,), interval=500)
        plt.show()

    plotrt = Process(target=plot, args=(q,))
    plotrt.start()

    def callback(indata, frames, time, status):
        if any(indata):
            q.put(indata)
        else:
            print('no input')

    with sd.InputStream(device=args.device, channels=1, callback=callback,
                        blocksize=int(samplerate * args.block_duration /50 ),
                        samplerate=samplerate) as sound:
        while True:
            response = input()
            if response in ('', 'q', 'Q'):
                break
            for ch in response:
                if ch == '+':
                    args.gain *= 2
                elif ch == '-':
                    args.gain /= 2
                else:
                    print('\x1b[31;40m', usage_line.center(args.columns, '#'),
                          '\x1b[0m', sep='')
                    break
except KeyboardInterrupt:
    parser.exit('Interrupted by user')
except Exception as e:
    parser.exit(type(e).__name__ + ': ' + str(e))