Einen Song fortsetzen

Wenn man anfängt mit dem kompinieren stößt man relativ schnell auf das Problem wie ein vorhandene Melodie erweitert werden soll. Beispielsweise hat man die Notenfolge C, G in LMMS oder in FL Studio erstellt und jetzt grübelt man darüber nach, ob man als nächstes ein A oder lieber doch ein E setzen soll. Auch viele Texte in denen algorithmisches Komponieren gelehrt wird, gehen mit dieser Fragestellung heran und häufig wird das Problem mit Hilfe von Markov Ketten gelöst, dass man also mit einer Wahrscheinlichkeit angibt, was nach einem G am besten zu folgen hat. Dieses Verhältnis zur Musik ist grundverkehrt, so komponiert niemand der echte Songs schreibt. Stattdessen geht es bei Musik darum, Noten zu finden, die genau zwischen zwei Noten passen. Das heißt, was nach der Notenfolge C, G kommen soll kann niemand sagen, auch Mozart weiß das nicht. Aber Mozart kann sagen, wie man von C nach G kommt.

Das Finden von Zwischennoten stellt die eigentliche Herausforderung innerhalb des Komponierens da. Im Grunde werden nach dieser Methode komplette Opern geschrieben. Als erste Note legt man das C fest und als letzte wieder ein C. Und der Raum den diesen beiden Noten abstecken gilt es zu füllen. Aber womit kann man ihn füllen? Am zweckmäßigsten mit sogenannten Pivotchords. Pivotchords sind im Grunde ein Algorthmus der angibt, wie man von Note 1 zu Note 2 gelangt. Man kann aber auch sehr viel komplexere Zwischenstufen sich ausdenken. Und wenn man hier ein System verwendet, hört sich das Ergebnis im Regelfall sehr ansprechend an. Das Finden von Zwischennoten kann man darüberhinaus noch fraktal gestalten. Ein Beispiel: Angenommen, man hat entdeckt, dass man von C nach G über die Note A gelangen kann. Die Folge wäre dann: C A G. Jetzt kann man erneut Zwischennoten suchen, um beispielsweise von C nach A zu gelangen und von A nach G. C D A E G, wäre eine mögliche Folge. Und so geht es dann immer weiter. Immer hat man eine Startnote und eine Zielnote. Da ein C gleichbedeutend ist mit C-Dur hat man in Wahrheit sogar 3 Noten auf einmal. Und jetzt muss man die Musiktheorie bemühen um zu entscheiden welche Noten guten klingen. Also welche Tonleitern sich überschneiden oder wie man eine Spannung erzeugt. Jenachdem wieviel Ahnung man von der Theorie hat, kann man einfache oder komplexe Zwischennoten finden.

Interessanterweise sehen das viele Musiker ein wenig anders. Da behaupten Bassisten gerne, dass Musik eigentlich ganz einfach wäre und sie mit nur 2 Akkorde einen kompletten Song spielen könnte. Rein formal mag das vielleicht stimmen, weil sie zunächst ein C-Dur spielen, dann ein G-Dur, dann wieder ein C-Dur usw. Das heißt, sie haben wirklich nur die beiden Akkorde die sie nacheinander spielen. Aber, ein Song ist das noch nicht. Wass die Bassisten vergessen haben zu sagen, ist dass sie zur Auflockerung dazwischen noch ganz andere Akkorde spielen, die dazu dienen etwas Abwechslung hineinzubringen. Und um diese Zwischenschritte zu finden muss man eben sehr wohl klassische Komposition beherschen, sonst klappt es nicht.

Wie man am besten den Traffic senkt

Um ehrlich zu sein, bin ich es leid darüber nachzugrübeln wie man den Traffic erhöht oder wie man Leute auf dieses blog lotsen kann. Alle Versuche sind gescheitert. Es ist schlichtweg aussichstslos zu versuchen, den Geschmack der Masse zu treffen, sein Google Ranking zu verbessern oder auch nur dafür zu stimmen, dass Google einen überhaupt in den Index aufnimmt. Was ich mir jedoch stattdessen überlegt habe ist ein Wettbewerb ganz anderer Art. Und zwar hat mir ein Blick auf die Statistik zu diesem Blog verraten dass ein Artikel bei den Lesern besonders gut ankommt und zwar trägt er den Namen „Das 7D Hologramm“. Ich habe darin die technischen Grundlagen der Holographie erläutert und versucht zu unterscheiden zwischen Realität und Fiktion. Offenbar kam das bei den Lesern gut an, der Text wurde relativ häufig aufgerufen.

Ich kann zwar den Traffic nicht erhöhen, aber was ich dafür kann ist das genaue Gegenteil. Und so habe ich den Artikel kurzerhand in den Papierkorb verschoben. Und nein eine lokale Kopie existiert auch nicht. Und wer den Text jetzt lesen will hat leider Pech gehabt. Genau das werde ich beibehalten, immer wenn irgendein Artikel einen erhöhten Traffic produziert wird er gelöscht.

Update: Robot-Control-System

Das hier häufiger thematisierte Robot-Control-System hat ein kleineres Update erhalten. Zum einen wurde die Entwicklungsumgebung von Eclipse auf Geany gewechselt. Aber auch in der Software selbst wurde etwas verändert. Der Box2D PPM Parameter wurde nach oben erhöht, dadurch reagiert die App wesentlich flüssiger. Fairerweise muss jedoch erwähnt werden, dass sie die Ablaufumgebung selber (Game-Engine + Physics-Engine) inzwischen eingependelt hat. Das heißt, es gibt einige Routinen um Objekte zu Joints zu definieren und das wars dann. Viel entscheidener dürfte die sogenante BehaviorEngine sein, also der Teil worüber letztlich der Roboter in der Simulation gesteuert wird. Hier wurde bei dieser Version ein Verfahren eingesetzt, was eine Finite-State-Machine mit Hilfe von linguistischen Methoden beschreibt. Damit ist gemeint, dass es sehr viele Subfunktionen gibt (mehr als 20 für ein simples Peg-in-hole-Problem) die sich lesen wie der Ablaufcode bei der Lego Mindstorms Challange. Es ist kein richtiges Programm, sondern eher so eine Art von Script wo feste Parameter genutzt werden. Diese Subfunktionen haben sprechende Namen erhalten, was aus programmtechnischer Sicht überflüssig ist, sondern eher dem Software-Engineering zuzuordnen ist.

Anders formuliert, es ist nicht gelungen den Abstraktionsgrad der Behavior Engine zu erhöhen. Es gibt nur Lowlevel Primitive die von der Simulation bereitgestellt werden wie moveto, rotate, opengripper und daraus setzt die BehaviorEngine in eine Art von Lexikon dann den Task zusammen. Nicht als deklaratives Modell sondern bottom up.

Die Programmbedienung selber ist simpel: nach dem Starten im Python Interpreter drückt man auf die Taste a, was die BehaviorEngine aktiviert. Diese führt dann den Peg-in-hole-Task aus. Der ist relativ umfangreich und endet irgendwann.

BEHAVIOR ENGINE
Gehen wir noch ein wenig auf die BehaviorEngine ein. Rein formal besteht sie aus einer Ansammlung von Makros welche jedesmal folgende Befehle enthalten:

self.setgoal((300,45))
self.taskpause()
self.setangle(180)
self.gripperopen(-1)

Diese werden mit wechselnden Parametern und in unterschiedlicher Reihenfolge aufgerufen. Dadurch werden Motion Primitive wie „posinit“ und „gripperdown“ bereitgestellt. Die Frage die sich stellt lautet, ob man programmiertechnisch das nicht hätte anders implementieren können. Beispielsweise als Tabelle wie beim Q-Learning wo man die Parameter automatisch generiert. Macht es überhaupt Sinn, manuellen Code zu schreiben und diesem auch noch ausführliche Namen zu geben, wenn absehbar ist, dass es kein richtiger Code ist? Keines der definierten Methoden ist für andere Aufgaben wie z.B. inverted pendulum swingup wiederverwendbar. Dort müsste man erneut die BehaviorEngine from scratch erstellen.

Auf der anderen Seite tut das Programm was es soll und man kann es leicht verändern. Das heißt, wenn in dem Script irgendwo ein Bug auftaucht, kann man schnell identifzieren welches Codesegment dafür verantwortlich ist, und dort einen Parameter manuell anpassen. Man kann das nicht so sehr als programmieren bezeichnen sondern eher mit Lochkarten-schreiben vergleichen für eine Musik-Orgel. Das heißt, nur der Teil vom Robot-Control-System der die Game-Engine selber beinhaltet ist echter Python Code, die BehaviorEngine selber ist so eine Art von natürlichsprachlichem Kommentar.

'''
titel: Physics Controller with many manuel methods
date: March 7, 2017
author: Manuel Rodriguez
'''
import time, sys, pygame, random, math, Box2D, os, numpy, threading
from Box2D.b2 import (world, polygonShape, staticBody, dynamicBody)
from Box2D import (b2CircleShape, b2FixtureDef, b2LoopShape, b2PolygonShape,
                   b2RevoluteJointDef, b2_pi)
from pygame.locals import *

class Settings(object):
  def __init__(self):
    self.screen = (400, 260)
    self.ppm = 100.0  # pixels per meter 100
    self.fric = 1.0  # 0.3 friction
    self.torque = 1000
    self.fps = 30  # 60 fps
    self.pause = 50000 / self.fps
    self.black = (0, 0, 0)
    self.grey = (150, 150, 150)
    self.red = (230, 0, 0)
    self.blue = (0, 0, 230)
  def box2d_to_pygame_pos(self, box2dpos):
    """ convert coordinates """
    x,y = int(box2dpos[0] * self.ppm),int(box2dpos[1] * self.ppm)
    return (x, y)
  def calcdistance(self, p1, p2):
    """ Euclidean ordinary distance """
    return math.sqrt((p1[0] - p2[0]) ** 2 + (p1[1] - p2[1]) ** 2)
  def angle_between_two_points(self, p1, p2):
    angle = math.degrees(math.atan2(p2[1] - p1[1], p2[0] - p1[0]))
    angle += 90
    if angle < 0: angle += 360
    return angle
  def polarpoint(self, p1, angle, radius):
    """ polar coordinates for a point on circle """
    angle = (angle - 90) * math.pi / 180
    x = p1[0] + radius * math.cos(angle)
    y = p1[1] + radius * math.sin(angle) 
    x,y=self.floattoint((x,y))
    return (x, y)
  def floattoint(self, p):
    return (int(p[0]), int(p[1]))
  def incircle(self,p1,radius,p2):
    """ checks if p2 is in circle """
    square_dist = (p1[0] - p2[0]) ** 2 + (p1[1] - p2[1]) ** 2
    return square_dist <= radius ** 2
  def getpixel(self, p):
    surf3d = pygame.surfarray.pixels3d(self.window)
    temp=surf3d[p[0]][p[1]]
    del surf3d # prevent blocking
    return temp

class Physics(Settings):
  def __init__(self):
    super(Physics, self).__init__()
    self.myworld = world(gravity=(0, 10), doSleep=True)  # 0,-10
    self.body = []
    self.joint = []
  def createbox(self,moveable,p,size):
    if moveable=="dynamic":
      self.body.append(self.myworld.CreateDynamicBody(position=p, angle=0))
      self.body[-1].CreatePolygonFixture(box=size, density=1, friction=self.fric)
    else:
      self.body.append(self.myworld.CreateStaticBody(position=p,
      shapes=polygonShape(box=size), angle=0))
  def createprismaticjoint(self,id1,id2,axisx,axisy,offset):
    """ create linear joint """
    self.joint.append(self.myworld.CreatePrismaticJoint(
        bodyA=self.body[id1],
        bodyB=self.body[id2],
        localAnchorA=(0, 0),
        localAnchorB=(0, offset),
        axis=(axisx, axisy),
        lowerTranslation=1,
        upperTranslation=3.2,
        enableMotor=True,
        enableLimit=False,
        maxMotorForce=self.torque,
        motorSpeed=0.0,
    ))
  def createrevolutejoint(self,id1,id2,offset1,offset2):
    """ create rotate joint """
    self.joint.append(self.myworld.CreateRevoluteJoint(
        bodyA=self.body[id1],
        bodyB=self.body[id2],
        localAnchorA=(offset1, 0),
        localAnchorB=(offset2, 0),
        enableMotor=True,
        enableLimit=False,
        lowerAngle=math.radians(60),
        upperAngle=math.radians(120),
        maxMotorTorque=100*self.torque,
        motorSpeed=0,
    ))
  def setspeed(self, id, speed):
    self.joint[id].motorSpeed = speed 
  def clearforce(self):
    for i in range(len(self.joint)): self.setspeed(i,0)
  def getpos(self, id):
    box2dpos = self.body[id].position
    return self.box2d_to_pygame_pos(box2dpos)
  def getangle(self, id):
    angle = self.body[id].angle
    angle = (math.degrees(angle)-90) % 360
    return angle
  def updatePhysics(self):
    self.adjustgripper()
    timestep = 1.0 / self.fps
    velocityIterations = 500
    positionIterations = 10
    self.myworld.Step(timestep, velocityIterations, positionIterations)

class Gripper(Physics):
  def __init__(self):
    super(Gripper, self).__init__()
    self.createbox("dynamic",(2, 1),(.1, .1)) # gripper center
    self.createbox("dynamic",(2.2, 0),(.4, .1)) # paddle bottom
    self.createbox("static",(2, 2.5),(2, .02)) # bottom
    self.createbox("static",(0.05, 1),(.02, 2)) # left
    self.createbox("static",(3.8, 1),(.02, 2)) # right
    self.createbox("dynamic",(1.2, 0),(.2, .2)) # box
    self.createbox("dynamic",(2, 1),(.3, .08)) # finger1
    self.createbox("dynamic",(2.2, 1),(.3, .08)) # finger2
    self.createbox("static",(2.3, 2.10),(.02, .3)) # wand
    self.createbox("static",(2.3, 1.05),(.02, .3)) # wand down
    self.createprismaticjoint(0,1,0,1,-.1)
    self.createprismaticjoint(1,2,1,0,.1)
    self.createrevolutejoint(6,0,.3,0)
    self.joint.append(self.myworld.CreatePrismaticJoint(
        bodyA=self.body[6],
        bodyB=self.body[7],
        localAnchorA=(0, 0),
        localAnchorB=(0, 0),
        axis=(0, 1),
        lowerTranslation=0,
        upperTranslation=.7,
        enableMotor=True,
        enableLimit=True,
        maxMotorForce=self.torque,
        motorSpeed=0.0,
    ))
    self.goal = (100,100)
    self.angle = 180
  def adjustgripper(self):
    # pos
    p1,p2= self.getpos(0), self.goal
    factor = 20
    diff = (p1[0] - p2[0], p1[1] - p2[1])
    dxy = (1.0 * diff[0] / factor, 1.0 * diff[1] / factor )
    self.setspeed(1, dxy[0])
    self.setspeed(0, dxy[1])
    # angle
    diff = self.getangle(6)- self.angle
    if diff<-180: diff=360-math.fabs(diff);
    factor=20.0
    da=diff/factor
    self.setspeed(2, da)
  def setangle(self,angle):
    self.angle=angle
  def setgoal(self,goal):
    self.goal=goal
  def gripperopen(self,speed):
    self.setspeed(3,speed)
      
class BehaviorTree(Gripper):
  def __init__(self):
    super(BehaviorTree, self).__init__()
    self.taskname="task1"
  def BTstart(self,taskname):
    self.taskname=taskname
    self.mythread = threading.Thread(target=self.taskmain)      
    self.mythread.daemon = True  # stops, if mainprogram ends
    self.mythread.start()
  def taskmain(self):
    print "task start"
    if self.taskname=="start": self.peginhole()
    print "task end"
  def peginhole(self):
    # self.getpos(0) # gripper
    # self.getpos(5) # box
    self.graspobject()
    self.objectinit()
    self.insertmain()
    self.releasegripper()
    self.correctivepush()
    self.movetorightside()
  def taskpause(self):
    pygame.time.wait(self.pause)
  def graspobject(self):
    self.movetograsp()
    self.gripperdown()
    self.gripperup()
  def movetograsp(self):
    p= (self.getpos(5)[0]-35,self.getpos(5)[1]-90)
    self.setgoal(p)
    self.gripperopen(1)
    self.taskpause()
  def gripperdown(self):
    p= (self.getpos(0)[0],self.getpos(0)[1]+25)
    self.setgoal(p)
    self.taskpause()
    self.gripperopen(-1)
    self.taskpause()
  def gripperup(self):
    p= (self.getpos(0)[0],self.getpos(0)[1]-50)
    self.setgoal(p)
    self.taskpause()
  def objectinit(self):
    self.rotateinit()
    self.posinit()
    self.insertintohole()
  def rotateinit(self):
    self.setangle(180-45)
    self.taskpause()
    self.setangle(90)
    self.taskpause()
  def posinit(self):
    p=(130,170)
    self.setgoal(p)
    self.taskpause()
    self.setangle(90+22.5) # rotate correction
    self.taskpause()
  def insertintohole(self):
    holepos=(228,180)
    diff=(holepos[0]-self.getpos(5)[0],holepos[1]-self.getpos(5)[1])
    goal=(20,35)
    change=(diff[0]-goal[0],diff[1]-goal[1])
    p=(self.getpos(0)[0]+change[0],self.getpos(0)[1]+change[1])
    self.setgoal(p)
    self.taskpause()
  def insertmain(self):
    self.rotateandpush()
    self.pushtoright()
  def rotateandpush(self):
    self.setangle(90)
    goal=(self.getpos(0)[0]+15,self.getpos(0)[1]+39)
    self.setgoal(goal)
    self.taskpause()
  def pushtoright(self):
    goal=(self.getpos(0)[0]+10,self.getpos(0)[1])
    self.setgoal(goal)
    self.taskpause()
  def releasegripper(self):
    self.gripperdown2()
    self.grippermoveaway()
  def gripperdown2(self):
    self.gripperopen(.2) # slow open
    self.taskpause()
    goal=(self.getpos(0)[0],self.getpos(0)[1]+5)
    self.setgoal(goal)
    self.taskpause()
  def grippermoveaway(self):
    goal=(self.getpos(0)[0]-50,self.getpos(0)[1])
    self.setgoal(goal)
    self.taskpause()
  def correctivepush(self):
    self.status()
    self.grippertobox()
    self.gripperpush()
    self.grippermoveaway()
  def status(self):
    p1= self.getpos(5) # box
    p2= (228,153) # ideal position
    diff=(p2[0]-p1[0],p2[1]-p1[1])
    print p1,p2,diff
  def grippertobox(self):
    goal=(self.getpos(0)[0],self.getpos(0)[1]-30)
    self.setgoal(goal)
    self.taskpause()
  def gripperpush(self):
    goal=(self.getpos(0)[0]+35,self.getpos(0)[1])
    self.setgoal(goal)
    self.taskpause()
  def movetorightside(self):
    self.moveoverup()
    self.moveoverright()
    self.fromrightinit()
    self.fromrightgrasp()
    self.fromrightrotate()
    self.fromrightrelease()
  def moveoverup(self):
    self.gripperopen(-1)
    self.setangle(90)
    self.taskpause()
    self.setgoal((100,45))
    self.taskpause()
  def moveoverright(self):
    self.setgoal((300,45))
    self.taskpause()
    self.setangle(180)
    self.taskpause()
    self.setangle(270)
    self.taskpause()
  def fromrightinit(self):
    self.setgoal((350,120))
    self.gripperopen(1)
    self.taskpause()
  def fromrightgrasp(self):
    self.setgoal((295,120))
    self.taskpause()
    self.gripperopen(-.2) # slow close
    self.taskpause()
    self.setgoal((350,120))
    self.taskpause()
  def fromrightrotate(self):
    self.setangle(180+45)
    self.taskpause()
    self.setgoal((300,120))
    self.taskpause()
  def fromrightrelease(self):
    self.setangle(180)
    self.setgoal((270,120))
    self.taskpause()
    self.gripperopen(1)
    self.taskpause()
     
class GUI(BehaviorTree):
  def __init__(self):
    super(GUI, self).__init__()
    pygame.init()
    self.window = pygame.display.set_mode(self.screen,HWSURFACE|DOUBLEBUF|RESIZABLE)
    self.clock = pygame.time.Clock()
    self.mouse = (0, 0)
    self.control = "manuel1"
  def updateGUI(self):
    #self.clock.tick(self.fps)  
    pygame.time.wait(1000/self.fps)
    self.window.fill((220, 220, 220))
    self.inputhandling()
    self.paintobjects()
  def paintobjects(self):
    text = str(self.control)
    text2 = " " + str(self.mouse)
    pygame.display.set_caption(text+text2)
    # box2d
    colors = { staticBody: self.grey, dynamicBody: self.blue, }
    for body in (self.myworld.bodies):  
      for fixture in body.fixtures:
        shape = fixture.shape
        if hasattr(shape, 'vertices'):  # rectangle
          vertices = [(body.transform * v) * self.ppm for v in shape.vertices]
          # vertices = [((v[0]), (self.screen[1] - v[1])) for v in vertices]
          pygame.draw.polygon(self.window, colors[body.type], vertices, 0)
        else:  # ball
          position = body.transform * shape.pos * self.ppm
          x, y = int(position[0]), int(self.screen[1] - position[1])
          radius = int(shape.radius * self.ppm)
          pygame.draw.circle(self.window, colors[body.type], (x, y), radius, 2)
          x2, y2 = x + radius * math.sin(body.angle), y + radius * math.cos(body.angle)
          pygame.draw.line(self.window, colors[body.type], (x, y), (x2, y2), 2)  # top
  def inputhandling(self):
    for event in pygame.event.get(): 
      if event.type == pygame.QUIT: 
        sys.exit(0) 
      if event.type == pygame.MOUSEMOTION:
        self.mouse = event.pos
        if self.control == "manuel1":
          self.goal = self.mouse
      if event.type == pygame.MOUSEBUTTONUP:
        pass
      if event.type == pygame.KEYDOWN:
        if event.key == pygame.K_1: self.setangle(self.angle-22.5)
        if event.key == pygame.K_2: self.setangle(self.angle+22.5)
        if event.key == pygame.K_3: self.gripperopen(-1)
        if event.key == pygame.K_4: self.gripperopen(1)
        if event.key == pygame.K_a: self.BTstart("start")
        if event.key == pygame.K_RIGHT: self.moveslow("right")
        if event.key == pygame.K_LEFT: self.moveslow("left")
        if event.key == pygame.K_UP: self.moveslow("up")
        if event.key == pygame.K_DOWN: self.moveslow("down")
        
class Game: 
  def __init__(self):
    random.seed()
    self.myGUI = GUI()
    for step in range(10000000):
      self.myGUI.updateGUI()
      self.myGUI.updatePhysics()
      pygame.display.update()  
 
if __name__ == "__main__":
  myGame = Game()

Das Dreikörperproblem

Das Dreikörperproblem war ursprünglich ein Problem für die Physik. Man hat im Weltraum drei verschiedene Planeten die sich gegenseitig mittels Gravitation anziehen. Das Rätsel ist jetzt wo sich die Planeten zu einem beliebigen Zeitpunkt befinden. Die Lösung für das Dreikörperproblem ist dann eine Formel die ungefähr so aussehen sollten: f(x)=y. Als x-Wert setzt man den Zeitpunkt ein, z.B. 1. Feb 2017 und als y-Wert erhält man dann die Position von Körper 1.

Gehen wir zunächst einen Schritt zurück und untersuchen ein Problem was relativ simpel ist. Man hat eine Sonne, und eine Erde die sich um die Sonne bewegt. Durch die Gravitation gibt es eine schöne Kreisbahn und man kann immer genausagen wo die Erde gerade ist. Auch hier wieder hat man eine Formel f(x)=y Und wenn man die Formel als Kurve einzeichnet erhält man eine Sinus-Funktion. Man setzt einfach einen beliebigen Zeitpunkt ein und kann dann im Plot das Ergebnis ablesen. Beim Dreikörperproblem ist die Sache schwieriger. Weil die Bewegung der Körper sehr viel komplexer ist. Noch relativ leicht lässt sich ermitteln wo die Körper sind, wenn man nur wenige Schritte in die Zukunft plant. Hier reicht es aus, eine Physik-Engine wie ODE zu verwenden, die drei Körper einzuzeichnen und eine physikalisch korrekte Simulation durchzuführen. Man kann dann für 10 Schritte in die Zukunft sehr leicht sagen wo die Körper sein werden, man lässt die Simulation einfach laufen.

Aber was ist, wenn man eine sehr weitreichende Prognose braucht, also wissen will wo sich die Körper in 1 Mio Zeitschritten befinden? Leider kann die ODE-Engine darauf keine Antwort geben. Man kann immer nur genau einen Schritt in die Zukunft rechnen. Um sehr viele Schritte durchzuführen, muss man das System rechnen lassen. Das Dreikörperproblem besteht darin, dass man einerseits nicht im Einzelschritt arbeiten will, aber dennoch wissen will in welchem Zustand sich das System in Zukunft befinden wird. Wie geht das?

Nach meiner Recherche lässt sich das Problem allein über die Mathematik oder über die Physik nicht lösen. Bis ungefähr dem Jahr 1900 wurde keine mathematische Theorie entwickelt, mit der man eine Physik-Simulation invers berechnen kann. Sondern man benötigt zwingend einen Computer plus Künstliche Intelligenz. Mit einem Computer allein ist das Problem ebenfalls nicht lösbar. Weil der Computer dank einer Physik-Engine wie ODE ja nur die sogenannten Forward-Simulation durchführen kann, also exakt sagen kann wo sich die Himmelskörper befinden aber immer nur für den nächsten Timestep. Um beliebige Timesteps zu rechnen benötigt man ein weiteres Stück Software, genauer gesagt ein Computerprogramm wo man als Inputparameter den Timestep vorgibt und das Programm dann die Koordinanten ausgibt. Die Frage aus Sicht der Informatik lautet jetzt: wie muss das Programm aussehen?

Hier gibt es verschiedene Ansätze. Eine besteht darin, neuronale Netze zu nutzen. Dabei betrachtet man das Program als Blackbox und versucht diese zu optimieren. Man nimmt Beispieldaten, ändert die Gewichte und erhält so einen Prognosewert. Das neuronale Netz gibt die Zielkoordinaten aus, allerdings mit einer Unsicherheit.

Leider sind neuronale Netze nicht die beste Methode. Auch damit gelingt es nicht das Problem zu lösen. Der Grund ist, dass bei der Simulation des Dreikörperproblems sehr viele Interaktionen möglich sind. Es ist also nicht so, dass man eine simple Zahlenfolge hat, die sich irgendwann wiederholt und wo man schnell ein Muster erkennt, sondern die Koordianten der Körper sind chaotisch, ein Muster ist nicht zu erkennen bzw. es ist sehr komplex.

Die meiner Meinung nach beste Methode das Dreikörperproblem zu lösen ist eine linguistische Finite State Machine. Damit ist gemeint, dass man das Problem zunächst nicht als mathematisch-physikalisch begreift sondern es als sprachliches Konstrukt versteht. Man beschreibt den Verlauf der Körper mit einer domänenspezifischen Sprache. Das heißt, denkt sich Begriffe aus und setzt diese in Beziehung. Dieses linguistische Netz wird dannn in eine Finite-State-Machine überführt.

Ein Beispiel: Man stellt sich vor wie ein Clown mit drei Bällen jongliert. Auf den ersten Blick ist das ein klassisches Physik-Problem. Jeder der Bälle hat eine Beschleunigung und eine Koordinate, vielleicht noch einen Drall. Aber, es ist auch und vor allem ein Clown der sein Publikum unterhält. Die Darbietung läuft so ab, dass zunächst alle Drei Bälle in der einen Hand sind und dann in die Luft geworfen werden. Ab da an an hält der Clown die Bälle auf einer Kreisbahn und versucht einen Ryhtmus auszubilden. Man merkt, dass er sich dabei anstrengt. Und irgendwann greift er falsch und ein Ball fällt herunter. Jemand im Publikum lacht.

Man kann ein und dieselbe Sache also auch mit Prosa beschreiben. Man muss nicht zwangsläufig Formeln verwenden. Mehr noch, wollte man versuchen die Prosa wegzulassen also die Tatsache ignorieren, dass da ein Clown mit einer roten Nase ist, würde man das eigentliche Problem nicht exakt beschreiben. Jetzt bleibt noch die Frage offen wie man aus einer umgangssprachlichen Schilderung eine Finite-State-Machine erzeugt.

PREDICTION MACHINE
Inwzischen ist klar geworden, was das Dreikörperproblem ist. Es geht darum, eine State-Machine zu konstruieren damit sie ein physikalisches System vorhersagt. Die passende GUI ist simpel: der Anwender gibt an, welchen Zeitpunkt er gerne berechnet haben möchte, kann anklicken ob das physikalische System störungsfrei durchläuft und kann auswählen ob er an den Koordinaten von Körper 1, 2 oder 3 interessiert ist.

Prototyp für ein Robot-Control-System

Die wichtigste Frage bei der Entwicklung eines Robot-Control-System ist nicht etwa wie es gemacht wird, sondern welche Methode nicht funktioniert. Ein wenig konnte ich das Thema bereits eingrenzen. Als dysfunktional haben sich herausgestellt:
– Neuronale Netze (nicht im Stande komplette Turing-Maschinen zu lernen)
– C++ als Implementierungssprache (zu viele Pointer, maximal 5x schneller als Python, zu viel Boilerplate-Code)
– WillowGarage ROS (allein die Installation ist eine Qual)

Es bleiben einige Techniken übrig, die sich als praktikabel hergestellt haben:
– Python, Eclipse, pydev, pygame, Box2D (ideale Entwicklungsumgebung, schnell programmierter Code)
– Finite-State-Maschines, Behavior Tree (gut geeignet um komplexe Abläufe zu spezifizieren)
– Python Threads (um einen Controller als Background Prozess zu starten)

Offen geblieben ist bisher die Frage, wie man eine Finite-State-Maschine so hochskaliert, dass sich damit komplexe Steuerungsprobleme lösen lassen. Bisher wurde lediglich ein Pick&Place Problem implementiert und bereits dort war das Schreiben der Motion Primitive extrem aufwendig. Eine Methode das zu beschleunigen gibt es nicht. Aber wie kann man herausfinden wo möglicherweise der Flaschenhals liegt? Gibt es womöglich eine Technologie die noch fehlt auf der Liste?

Um das herauszufinden bietet es sich an, mit Prototypen zu arbeiten, also mit nicht funktionalen Mockups, die schnell erstellt sind und die das Prinzip als solche erläutern. Um einen Mockup für Behavior Trees zu erstellen, lässt man die Details einfach weg und betrachtet die Sache auf rein linguistischer Seite. Es geht darum, nur die Abfolge der Motion Primitive zu ermitteln und ein kleines Python Programm zu haben, mit dem man herumspielen kann. Im folgenden dazu der Sourcecode:

'''
Created on 20.02.2017
Prototyp BehaviorTree
@author: hp
'''
import time, sys, random, math, numpy, threading, logging
logging.basicConfig(
  stream=sys.stdout,
  level=logging.DEBUG,
  format='%(message)s'
)
log = logging.getLogger(__name__)

def taskpause():
  time.sleep(1)
def main():
  log.info('main')
  walk()
  pickplace()
def pickplace():
  log.info('pickplace')
  grasp()
  move()
  ungrasp()
  grasp()
  move()
  place()
def walk():
  log.info('walk')
  centertoright()
  footup()
  footforward()
  centertoleft()
  footup()
  footforward()
  centertoright()
def footup():
  log.info('--footup')
  taskpause()
def footforward():
  log.info('--footforward')
  taskpause()
def centertoleft():
  log.info('--centertoleft')
  taskpause()
def centertoright():
  log.info('--centertoright')
  taskpause()
def place():
  log.info('-place')
  taskpause()
def ungrasp():
  log.info('-ungrasp')
  down()
  opengripper()
  up()
def grasp():
  log.info('-grasp')
  grasptype()
  move()
  opengripper()
  down()
  closegripper()
def grasptype():
  log.info('--grasptype')
  taskpause()
def move():
  log.info('--move')
  taskpause()
def opengripper():
  log.info('--opengripper')
  taskpause()
def closegripper():
  log.info('--closegripper')
  taskpause()
def down():
  log.info('--down')
  taskpause()
def up():
  log.info('--up')
  taskpause()
  
if __name__ == "__main__":
  main()

Das Programm kann man ganz normal ausführen und auf dem Bildschirm werden dann die Logausgaben angezeigt. Durch den Pause-Befehl ist das ganze verzögert, so dass man schön in Ruhe mitlesen kann. Es handelt sich dabei um einen BehaviorTree der aus hierarchichen Subfunktionen besteht ohne dass er etwas tut. Vielmehr ist es ein reines Mockup-Projekt. Es zeigt, wie man aus einfachen Befehlen komplexe Befehle zusammenbaut. So besteht der Task „grasp“ aus einer Abfolge von weiteren Befehlen, die nacheinander aufgerufen werden. Obwohl programmiertechnisch das ganze harmlos aussieht, ist diese Form der Programmierung relativ selten. Denn normalerweise werden die KI-Bots anders programmiert und zwar als Gametree Search, wo also die CPU belastet wird und irgendwas durchprobiert wird. Im obigen Fall hingegen wurde alles manuell programmiert, es ist eine hierarchiche Finite-State-Maschine die linear durchläuft.

Leider ist es nicht gelungen, den Mockup an Grenzen zu führen, also aufzuzeigen in welchen Fällen er versagt. Man kann zwar ein wenig mit dem Pause-Paremter herumspielen um das Programm schneller oder langsamer zu machen, an dem Behavior tree ändert sich jedoch nichts. Es handelt sich um eine Art von Authoring-Werkzeug um beliebig komplexe Sachverhalte zu gliedern. Man kann noch weitere Funktionen erstellen, die entweder vorhandene Motion Primitive neu anordnen, oder sogar neue Motion Primitive benötigen. Um soetwas wie einen Fail zu versuchen müsste man den Prototypen um eine Game-Engine erweitern. Also gegen eine Baseline Programmieren wo ein Task entweder erfolgreich ausgeführt wird oder eben nicht.

VERBESSERUNGSMÖGLICHKEITEN
Die nächste Stufe bei der Entwicklung eines Prototypen besteht darin, nicht nur ein text-log auszugeben, sondern die Aktionen grafisch zu visualisieren. Dazu wird ein vereinfachter Roboter verwendet, der in der Luft hängt und selbst nicht laufen kann. Sondern es werden einfach die Kommandos an die Gliedmaßen gesendet, die bewegen sich dann auch, aber irgendein Ziel ist damit nicht verbunden. Es geht also darum, die textuelle Ausgabe durch eine Visualisierung zu ersetzen. Es würde dann auf dem Bildschirm nicht nur dastehen „footforward“, sondern auf dem Bildschirm bewegt sich der Fuß dann tatsächlich. Allerdings noch ohne Kollisionsabfrage oder ähnliches.

1

Wenn man diese Idee in Source realisiert ergibt sich die obige Abbildung. Programmtechnisch war es etwas anspruchsvoller als die reine Textausgabe, aber es ist noch immer ein Mockup. Das heißt, der Roboter auf dem Bildschirm bewegt sich nicht wirklich, sondern es sind einfach nur 4 Motoren die in der Luft hängen und über ein Script gesteuert werden. Neu ist jedoch, dass man jetzt nicht nur Textbotschaften auf der Komamndozeile durchlaufen sieht, sondern die beiden Füße visualieren kann. Man drückt auf Start und sieht dann eine synchron ablaufende Bewegung. Das interessante daran ist, dass sie anfangs noch Fehler enthält, die man dadurch fixt, dass man das Script verbessert. Man kann sich aus den Motion Primitiven dann höherwertige Tasks zusammenbauen. Eine Baseline gegen die man programmiert gibt es ähnlich wie in dem rein textuellen Mockup nicht. Das heißt, der Roboter muss nicht wirklich vorwärtsgehen oder eine Punktzahl erreichen. Vielmehr ist das ganze eine simple Demonstration, also wie man über ein Script eine längere Animation erstellt.

Das interessante daran ist, dass sich nur mittels Finite-State-Maschine sehr komplexe Abläufe scripten lassen. Auf der Lowlevel-Ebene hat man nur den Befehl rotate zur Verfügung um eine Gliedmaße auf eine Sollposition zu bewegen. Aber daraus kann man sich dann Befehle erstellen wie „Footleftup“ oder „Rest“, wodurch dann mehrere Subaktionen ausgeführt werden.

Das Konzept unterscheidet sich grundsätzlich von Reinforcement Learning oder DeepLearning wie es häufiger in der Literatur diskutiert wird. Im Kern gibt es eine manuell programmierte Finite State Maschine die in einem Authoring-Prozess erweitert wird. Es ist also zwingend ein Man-in-the-loop nötig um die Bewegungsabfolge zu scripten. Die Software findet also nicht durch Suche im Problemraum von allein die Lösung. Der Clou ist, dass das Scripten jedoch simpel ist, weil man mit selbst definierten High-Level Kommandos arbeitet.

Da das Gesamtprogramm wegen der Pygame und Box2D Bibliothek umfangreich ausfällt im folgenden nur die eigentliche Behavior Engine um die Füße zu animieren:

  def task2(self):
    self.taskrest()
    self.taskrightup1()
    self.taskleftup1()
    self.taskrightup2()
    self.taskleftup2()
    self.taskrightdown1()
    self.taskleftdown1()
    self.taskrightdown2()
    self.taskleftdown2()
  def taskrest(self):
    log.info('rest')
    self.taskrotate(0,0)
    self.taskrotate(1,0)
    self.taskrotate(2,0)
    self.taskrotate(3,0)
    self.taskpause()
  def taskwalkstart(self):
    log.info('walkstart')
    self.taskrotate(1,-90)
    self.taskrotate(3,-90)
    self.taskpause()
  def taskrightdown1(self):
    log.info('rightdown1')
    self.taskrotate(0,0)
    self.taskrotate(1,-90)
    self.taskpause()
  def taskrightdown2(self):
    log.info('rightdown2')
    self.taskrotate(0,90)
    self.taskrotate(1,0)
    self.taskpause()
  def taskleftdown1(self):
    log.info('leftdown1')
    self.taskrotate(2,0)
    self.taskrotate(3,-90)
    self.taskpause()
  def taskleftdown2(self):
    log.info('leftdown2')
    self.taskrotate(2,90)
    self.taskrotate(3,0)
    self.taskpause()

  def taskrightup1(self):
    log.info('rightup1')
    self.taskrotate(1,-90)
    self.taskpause()
  def taskrightup2(self):
    log.info('rightup2')
    self.taskrotate(0,-90)
    self.taskrotate(1,-180)
    self.taskpause()
  def taskleftup1(self):
    log.info('leftup1')
    self.taskrotate(3,-90)
    self.taskpause()
  def taskleftup2(self):
    log.info('leftup2')
    self.taskrotate(2,-90)
    self.taskrotate(3,-180)
    self.taskpause()

Wie man sieht sind auf der untersten Werte absolute Winkelangaben verwendet worden, die dann zu komplexeren Befehlen aggregiert werden. Wenn man das etwas schneller abspielt ergibt sich eine Fließende Bewegung die man hintereinanderweg ausführen kann. Es ist ähnlich als wenn man eine Kurvenscheibe verwendet, nur dass man als Motion Primitive eine Finite-State-Machine nutzt. Im Hinblick auf die vorhandene Robotik-Literatur kann man das als vereinfachte Kopie von Marc Raiberts Arbeit betrachten, die in den 1980’er veröffentlicht wurde. Nach diesem Prinzip lassen sich biped Walking Roboter konstruieren.

Man kann jetzt im Detail debattieren ob man die Winkelangaben über einen Solver automatisch bestimmt und welche Formeln man noch einbaut, um die Stabilität des Walking-Zyklus zu gewährleisten. Die Idee als solche jedoch mittels Finite-State-Machine einen Controller zu realisieren ist jedoch konstant.

Natürlich ist der obige Sourcecode nicht der erste seiner Art, das Video zum Rhex Roboter wurde schon viel früher veröffentlicht und dort ist nicht nur ein Mockup sondern ein kompletter Roboter zu sehen. Aber, Rhex wurde nicht öffentlich dokumentiert, es gibt keinen Sourcecode oder ähnliches. Das obige Programm hingegen ist auf Verständlichkeit hin ausgelegt, das heißt es wird im Detail erläutert wie der Zaubertrick funktioniert und was relevant ist und was nicht. Das Problem mit Rhex ist weniger, dass der Roboter keine Leistung erbringen würde, sondern das Problem mit RHEX ist, dass es er nicht verrät wie diese Leistung reproduziert werden kann. Aus didaktischer Sicht ist also die Arbeit im RHex Projekt und die Paper von Marc Raibert kein produktiver Beitrag gewesen für die Robotik.

Na ja, zugegeben wenn man bei Google Scholar dezidiert nach „Rhex Finite State Maschine“ sucht findet man einige Paper wo das Prinzip im Detail erläutert wird. Aber es ist eben versteckt, man muss schon sehr genau danach suchen. Wenn man nur oberflächlich in Erfahrung bringen möchte wie RHEX programmiert wurde, wird man eher als Erklärung finden, dass das dort Reinforcement Learning eingesetzt wurde. Das mag zwar der Fall sein, aber das ist nur ein Nebenproblem beim Entwickeln eines Controllers.

WALKING
Spannend wird es, wenn man den Mockup erweitert. Bisher haben sich die Beine nur in der Luft bewegt, der Roboter war quasi aufgebockt. Wenn man jedoch die Box2D Parameter so verändert, dass die Beine den Boden berühren und man dann die Finite-State-Maschine startet ergibt sich etwas erstaunliches. Und zwar kann man den Controller in echt sehen. Das heißt, es werden Motion Primitive ausgeführt und diese führen in der Box2D Engine zu einem Resultat. Natürlich nicht zu dem gewünschten, dass der Roboter graziel sich vorwärtsbewegt. Eher erinnern die Bewegungen an Zufallsbewegungen. Aber, sie kommen nicht per Zufallsgenerator zustande sondern sind das Resulat der BehaviorEngine die streng nach Taskplan durchläuft.

Und jetzt passiert etwas erstaunliches. Es ist mit ein wenig herumprobieren leicht möglich, die Finite-State-Maschine so umzuprogrammieren dass eine Vorwärtsbewegung entsteht. Dazu versucht man zunächst rein manuell die Bewegung zu erzeugen. Das heißt, man bringt ein Bein in eine bestimmte Position und danach in eine andere. Und wenn dadurch sich der Roboter bewegt, notiert man diese Aktionen einfach in die Finite State Machine. Und damit kann man jetzt das Makro beliebig oft wiederholen. Man hat einen ersten Mini-Controller der zwar nicht besonders intelligent ist, aber zumindest auf Knopfdruck den Roboter vorwärtsschiebt.

Der Form halber hier der Minimal-Controller:

  def task2(self):
    self.taskrotate(3,-90) # init
    self.taskpause()
    for i in range(10):
      self.taskrotate(2,-22.5)
      self.taskpause()
      self.taskrotate(2,0)
      self.taskpause()

Er ist stark auf die jeweilige Umgebung hin zugeschnitten. Am Anfang wird das hintere Bein in die Init Position gebracht und dann wird mehrmals hinterinander eine Abfolge ausgeführt. Auf dem Bildschirm ergibt sich eine Oszilator-ähnliche Bewegung. Das heißt, darüber wird der gesammte Roboter jeweils ein kleines Stück vorwärtsbewegt.

Natürlich ist das noch sehr provisorisch und vermutlich kann man den controller so erweitern, dass die Beweguhg schneller ist, also in der selben Zeit mehr Wegstrecke zurückgelegt wird. Doch das ganze ist nur ein Mockup, wichtig ist, dass auf Knopfdruck sich der Roboter überhaupt vorwärtsbewegt. Wenn man jetzt noch einen weiteren Motion Primitive für die Rückwärtsbewegung erstellt kann man die Makros dann auf unterschiedle Tasten legen und darüber den Roboter steuern. Ein sehr mächtiges Prinzip wie ich finde.

VERALLGEMEINERUNG
Um auf das Eingangsstatement zurückzukommen. Der Blogpost hat zunächst in Python-Behavior Tree erstellt, bei man man mitlesen konnte welche Motion Primitive ausgeführt wurden. Das textuelle Konzept wurde erweitert um eine visuelle Komponente und diese zu einem richtigen Roboter. Dafür wurde dann ein mini-Vorwärts-Controller programmiert der den Roboter bewegt. Spannend ist, dass der Roboter einerseits eine Bewegung ausführt man aber gleichzeitig in der Statusleiste mitlesen kann, welche Motion Primitive aktuell aufgerufen werden. Das heißt, man sieht durchlaufen, dass Joint 2 auf Positon -22,5 und dann auf Position 0 gebraucht wird, genauso wie es im Taskplan drinsteht. Man hat zwar noch nicht automatisch den perfekten Controller, aber man hat eine Entwicklungsumgebung worüber man ihn programmieren kann.

Hier mal ein Beispiel für einen etwas komplexeren Controller bei dem Subfunktionen verwendet wurden. Damit bewegt sich der Roboter 5 Schritte nach rechts und dann 5 Schritte nach links:

  def task2(self):
    log.info('init')
    self.taskrotate(3,-90) # init
    self.taskpause()
    for i in range(5):
      self.taskright()
    for i in range(5):
      self.taskleft()
  def taskright(self):
    log.info('-- right')
    self.taskrotate(2,-22.5)
    self.taskpause()
    self.taskrotate(2,0)
    self.taskpause()
  def taskleft(self):
    log.info('-- left')
    self.taskrotate(0,22.5)
    self.taskpause()
    self.taskrotate(0,0)
    self.taskpause()

Über den log.info Befehl wird parallel noch ausgegeben, in welchem Mode der Roboter gerade ist. Man erhält so eine sehr übersichtliche Statusanzeige und kann den Controller bugfixen wie ein normales Computerprogramm. Der Witz ist, dass man diesen höherwertigen Controller dann erneut wieder in einen noch höherwertigeren Controller packen kann. Also eine Funktion programmieren, die den Roboter zu einer bestimmten Zielfunktion bringt und dafür dann die Submakros einsetzt. Im Grunde ist das die Realisierung der Subsumption Architektur wie sie Brooks beschrieben hat. Ja, sie funktioniert, ja man kann damit Roboter-Controller bauen.

WIE WEITER?
Das vorgestellte Prinzip lässt sich in sehr viele Richtungen erweitern. Beispielsweise könnte man ein Bein #3 hinzufügen. Dadurch könnte man dann immer 1 Bein in der Luft lassen wodurch sich neue Makros ergeben, die neue Fortbewegungen ermöglichen. Man würde sich zuerst ein Motion Primitive bauen um ein bestimmtes Bein anzuheben und mit diesem Motion Primitive könnte man dann einen Walkzyklus bauen. Ich glaube, der entscheidene Clou in dem Verfahren ist, dass man viele Dinge verzichtet die normalerweise in der Literatur einen hohen Stellenwert haben, insbesondere die Illusion man könnte über Reinforcement Learning und genetische Algorithmen irgendwie automatisch den Motion Controller synthetisieren. Viel effektiver ist es, in einem Trial & Error Verfahren sich selber die Motion Primitive zu basteln und diese zu High-Level-Controllern zu aggregieren. Rein theoretisch wäre es vielleicht möglich, mittels genetic Programming das auch automatisch zu machen — nur wozu? Was man möchte ist im Normalfall einen Controller der noch Bugs enthält und die man fixen kann. Beispielsweise kann man sich den oben abgedruckten Controller ansehen und Verbesserungsvorschläge unterbreiten und die dann umsetzen. Das geht bei Genetic Progrmaming nicht.

Vertrauen in die Wissenschaft gesunken

Viele Vertreter des akademischen Betriebens betrachten Wissenschaft und universitäre Wissenschaft als Einheit. Dabei steckt die Wissenschaft in einer Legitimationskrise. Es geht keineswegs um die Frage, ob Biologie, Mathematik oder Informatik an sich das richtige Werkzeug sind um etwsa über die Welt zu erfahren sondern es geht darum ob die TU-Berlin, das MIT oder der Elsevier Verlag die richtigen Orte sind um Wissenschaft zu betreiben. Das heißt, es gibt eine Glaubwürdigkeitskrise. Daran ist die universitäre Wissenschaft selber Schuld. Zu nennen sind Skandale mit Plagiaten, wo also Doktortitel vergeben werden obwohl der Begutachter wusste dass ein Ghostwriter und nicht der Student der Verfasser war. Aber auch die Tatsache, dass bis heute Wissenschaftliche Ergebnisse nicht frei verfügbar sind und OpenAccess eher die Ausnahme als die Regel ist haben dazu beigetragen dass sich viele Stundenten die etwas lernen wollen dies außerhalb von universitären Institutionen tun. Man hat bei vielen Studiengängen den Eindruck als würde es um alles mögliche gehen, nur nicht darum etwas über die Naturwissenschaft zu lernen oder neue Dinge zu erforschen. Im einfachsten Fall sind die Bibliotheksöffnungszeiten einfach nur unpraktisch. Das heißt, als Student möchte man gerne am Sonntag vormittag etwas lesen, da hat die Bibliothek jedoch geschlossen. Solche Kleinigkeiten sind noch kein Weltuntergang und sind geeignet das Vertrauen in die Universität als solche zu untergraben. Aber wenn man häufiger damit konfrontiert wird, dass der Zugang zu Wissen reglementiert wird beispielsweise durch Paywalls, durch vergriffene Bücher, durch falsche Informationen die veröffentlicht werden und durch falsche Anreizsysteme, dann stellt sich die grundsätzliche Frage ob Wissenschaft so wie sie derzeit an den Hochschulen betrieben wird zukunftsfähig ist.

Ich glaube, dass Dinge wie Mathematik, Informatik oder Geschichte immer eine Zukunft haben werden. Es ist jedoch fraglich, ob die Verzahnung zwischen diesen Fächern und staatlich kontrollierten Universitäten der richtige Weg ist. Bildung ist schon immer Spielball von politischer Einflussnahme gewesen. Zu glauben, dass die Interessen einer Hochschule und die der Studenten deckungsgleich sind, verdeckt die Probleme anstatt sie anzusprechen. Wenn man etwas genauer hinschaut wird man feststellen, dass sich spieltheoretisch die Interessen sogar entgegenstehen. Viele Universitäten sind kommerzielle Unternehmen mit einer Gewinnerzielungsabsicht. Ihre Aufgabe ist es, Bücher zu verkaufen, Forschungsgelder einzuwerben oder Werbung für Firmen zu machen. Bildung ist nur Mittel zum Zweck. Und häufig wird Wissenschaft von den Universitäten vereinnahmt um ganz andere Ziele zu verfolgen.

Es gibt zwei Möglichkeiten wie man darauf reagiert. Entweder könnte man versuchen, das universitäre System zu reformieren. Oder aber man setzt auf eine Gegenaufklärung die außerhalb von Universitäten stattfindet. Ähnlich wie Blogs ein Gegengewicht zu den Printmedien bieten könnte mittels Web 2.0 Techniken eine Alternative zur Universität entstehen. Damit ist nicht gemeint, eine alternative Wissenschaft zu betreiben die mit Homöopathie und Wahrsagerei arbeitet, sondern es geht darum, Universität und Bildung zu trennen. Also Naturwissenschaft außerhalb von Universitäten zu betreiben.

Wie kann soetwas konkret aussehen? Zu nennen sind sie hier wissenschaftliche Plattformen wie Apple iTunes U, Google Scholar, Academia.edu, ResearchGate, Wikipedia und Question&Answering Webseiten. Dort geht es um wissenschaftliche Themen aber außerhalb eines universitären Rahmens. Die Schwierigkeit besteht darin, die Leute zu motivieren nicht nur als Konsument sondern auch als Produzent von Wissen aufzutreten. Wikipedia ist für dieses Mißverhälttniss ein gutes Beispiel. Diese Wissensplattform hat in der Bevölkerung einen Bekanntheitsgrad von 100% aber nur sehr wenige Leute sind dort als Autor aktiv. Derzeit spricht man von rund 5000 Leuten in Deutschland die dort Content hochladen. Auch Academia.edu hat mit fehlendem Content zu kämpfen. Von den Universitätsangehörigen gibt es kein Interesse dort etwas einzustellen weil man ja ohnehin in etablierten Zeitschriften publiziert und Leute die nicht im universitären Bereich beheimatet sind, haben ebenfalls Vorbehalte weil sie überfordert sind, ein 8 Seiten Paper zu schreiben was wissenschaftlichen Ansprüchen genügt.

Ein Gebiet auf dem die universitäre Wissenschaft versagt hat ist das Thema Künstliche Intelligenz. Aus Sicht der Wissenschaft ist dieses Thema das wichtigste überhaupt, Künstliche Intelligenz ist eine Art von Universalwissenschaft von der sich alles andere ableitet. Es steht oberhalb der Naturwissenschaft und oberhalb der Geisteswissenschaft. Universitäten sind jedoch nicht willens und nicht in der Lage dieses Thema angemessen zu beleuchten. Das ist kein rein deutsches Problem sondern ein internationales Phänomen. Es gibt zwar an den Universitäten Vorlesungen über LISP und Robotik, doch das ist nicht Künstliche Intelligenz. Sondern das ist eine universitäre Künstliche Intelligenz von Leuten, die im Wissenschaftsbetrieb aktiv sind und dort Desinformation betreiben. Besonders deutlich kann man das sehen wenn es um eine populäre Darstellung des Themas in den Medien gibt. Im Regelfall kommen dort Leute wie Ray Kurzweil und Rodney Brooks zu Wort, die auf Panels dem Publikum erklären wie die Zukunft aussieht. Informationen gibt es dort keine, sondern es wird in Klippschulmarnier von der Kanzel herab doziert.

Diese Schwäche des Systems hat strukturelle Gründe. Die Universitäten sind nicht im Stande eine andere Form des Unterrichts anzubieten. Sie können nur BigBusiness und Propaganda betreiben. Sie sind von ihrem Selbstverständnis nicht in der Lage freies Wissen, leicht verständliches Wissen und praktisch einsetzbares Wissen zu produzieren. Obwohl Wikipedia und ResearchGate unzweifelhaft Wissenschaft auf hohem Niveau sind, sind sie eine Gefahr für die Universitäten. Fester Bestandteil von Universitäten war es immer dass nicht nur Wissen produziert wird, sondern auch die Gesellschaft geformt wird. Das heißt, es werden Studenten ausgebildet die dann als Beruf Wissenschaftler sind. Bei Wikipedia geht es um etwas anderes. Wikipedia hat vom Selbstverständnis das Ziel alle Menschen zu bilden. Wikipedia ist stolz darauf, wenn möglichst viele Leute die Seite besuchen. Schaut man sich etwas den Kontext an, aus dem Wikipedia entstanden sind, so ist es nicht als universitäres Projekt wie Arxiv entstanden. Sondern Jimmy Wales war früher einmal Händler für Future-Kontrakte an der Chicagoer Börse. Er ist also kein Universitätsangehöriger. Andere Leute, die das Projekt mitgeründet haben, waren davor in der Werbebranche tätig. Das heißt, vom Background her verkörpert Wikipedia nicht die Werte, die am MIT und in Stanford gelten.

Interessanterweise sind die meisten Autoren, die bei Wikipedia mitschreiben keineswegs dem universitären Establishment zuzuordnen. Es sind also keine Professoren die an Universitäten unterrichten. Von Rodney Brooks wird man in 100 Jahren nicht erleben, dass er bei Wikipedia auch nur irgendwas editiert. Sondern der Durchschnittswikipedianer ist eine Stufe darunter. Es sind Lehrer, ehemalige Universitätsangehörige, Ghostwriter und Quereinsteiger. Also klassische Dropouts, die im Universitätssystem keine Zukunft haben.

Sun Tzu und die Kunst ein Lexikon zu schreiben

Sun Tzu sagt, jede Schlacht ist geschlagen bevor sie begonnen hat. Im Beispiel von Wikipedia heißt das, dass man bevor einen Text überhaupt dort einstellt man wissen sollte, wie darauf die Gegenseite reagiert. Um das herauszufinden habe ich folgendes Python Script erstellt:

# -*- coding: utf-8 -*-
'''
Created on 22.02.2017
Wikipedia Check
@author: Manuel Rodriguez
'''
class Quality: 
  def __init__(self):
    self.sentences = input('How much sentences? ')
    self.sources = input('How much sources from Google Scholar? ')
    self.doi = input('... from that with a DOI number? ')
    self.orthography = input('Is orthography correct? (0=no, 1=yes) ')
    self.quotationmarks = input('Are the quotation marks changed from"that" to „that“? (0=no, 1=yes) ')
    self.result()
  def result(self):
    self.neededcitation = 5
    self.neededdoi = 0.5
    count1 = 1.0 * self.sources / self.sentences
    count2 = 1.0 * self.doi / self.sources
    cond1,cond2,cond3,cond4=False,False,False,False
    if count1 >= (1.0*1 / self.neededcitation): cond1 = True 
    if count2 >= (1.0*self.neededdoi): cond2 = True
    if self.orthography==1: cond3=True
    if self.quotationmarks==1: cond4=True
    print "\nResult: ",
    if cond1==True and cond2==True and cond3==True and cond4==True:
      print "Text is great, please upload. Sighting can take up to 40 days"
    else: 
      print "Textquality is bad, Don't upload. The problems are:"
      if cond1==False: print "- number of sources"
      if cond2==False: print "- number of DOI"
      if cond3==False: print "- orthography"
      if cond4==False: print "- quotation marks"
 
if __name__ == "__main__":
  myQuality = Quality()

Nachdem man das Programm mit „python check.py“ startet muss man auf der Komamndozeile einige Fragen beantworten: Anzahl der Sätze, Anzahl Quellen, Anzahl Quelllen mit DOI Nummer, Rechtschreibung korrekt? und Anführungszeichen korrekt? Dann wird überprüft ob die Wikipedia-Qualitätskriterien erfüllt sind und entweder erscheint dann, dass alles super ist oder eben nicht. Der Check bietet eine erste Möglichkeit grob einzuschätzen ob sich ein Upload überhaupt lohnt. Vielleicht mal ein kleines Beispiel.

Auf meiner Festplatte habe ich schon einen kleinen Wikipedia Artikel geschrieben über ein Thema was interessant ist, und wozu es noch keinen Artikel bei Wikipedia gibt. Wenn ich jetzt das Programm starte, gebe ich bei Anzahl Sätze wahrheitsgemäß „10“ ein, bei Anzahl Quellen „1“ bei Anzahl Quellen mit einer DOI Nummer „0“, bei Rechtschreibung und Anführungszeichen beidesmal eine „0“ für nicht eingehalten. Die Software rechnet dann ein wenig und es wird ausgegeben, dass die Textqualität zu niedrig ist. Jetzt könnte ich natürlich den Text trotzdem zu Wikipedia hochladen. Nur, die Wahrscheinlichkeit ist hoch, dass es Stress gibt. Entweder gibt es einen Löschantrag oder ein erfahrener Admin hat daran etwas auszusetzen. Insofern ist es besser auf den Upload zu verzichten. Das ist zwar schade aber nicht zu ändern.

Wer sich den Sourcecode näher anschaut wird relativ schnell bemerken was man eingeben muss, damit die Software den Text für perfekt hält: man muss alle 5 Sätze mindestens eine Quelle haben und es müssen 50% DOI Quellen sein. Bei den Fragen 4 und 5 muss man beidesmal „1“ eingeben. Und ja, darüber kann man den Check austricksen.