This article is the 22nd day article of "DSL Advent Calendar 2019".
Christmas, New Year's Eve, New Year, and the big events are coming soon. It seems to be the season when it gets buoyant and less busy. How are you all doing? At the end of the Advent calendar, the fact that it is being turned by a small number of people is just before the mental collapse, The solo player who continues to write until today is about to graduate from humans.
The members of this ad-care are related to DSL, but I will participate in the OB frame! I graduated from an undergraduate school and am an engineer at a certain IT venture. I would like to summarize and introduce what I have studied for about half a year after joining the company.
Now, in order to perform machine learning on the Web, you need to be aware of the following points.
--You have to keep the web server running while running time-consuming processes such as pre-processing, learning, and prediction. --It may be necessary to operate the graphic memory at the start and end of processing.
In order to deal with this point, it is possible to manage the start and end processing of each process in a multi-process manner. Program the system. ~~ It's annoying. ~~
First is the non-blocking IO monument async / await. If you have touched the front end, you may use it as a matter of course. In fact, it's also in Python.
However, unlike javascript async / await, functions with async always have a coroutine object. It returns, so it can only be executed inside an event loop.
There is a concept called System of Systems as a concrete method of designing a system. Originally not in software design but in other fields such as business processes Is it like something used? But this time, I'll put this into the process management part.
A system consists of zero or more systems. At this time, the child system is called a subsystem with respect to the parent system. When all subsystems have been booted, the parent system is treated as "booted". When all subsystems are terminated, the parent system is treated as "closed".
The system takes the states shown in the table below. The states that can be transitioned from each state are fixed, and it is not possible to transition from initial to running etc. suddenly.
Status | Description | Transition is possible |
---|---|---|
initial | The state given as the initial value immediately after the system is created | ready, disabled |
ready | A state that indicates that the system is ready to run | running |
running | State when the system is running | completed, intermitted, terminated |
completed | A state indicating that the system has completed execution normally | - |
disabled | It is possible to transition to ready by removing the state indicating that the system cannot be executed and the cause of the inexecutability. | ready |
intermitted | A state that indicates that the system is down, you can go back and forth between interfered and running as many times as you like while the system is running.(It's difficult to actually make it that way) | running |
terminated | The state when the system is forcibly terminated, unlike disabled, cannot be transitioned from here | - |
The figure below is a simple state transition diagram. If the process proceeds normally without any error on the way, it will take the blue route. If the process cannot proceed due to an unexpected situation, it will be disabled or terminated through the red route. In addition, the transition of the green route is basically started by human judgment and operation.
In the previous section, we introduced each state of the system and defined it. Next, we will define the state transition, or the arrow in the figure. The definition is a bit stubborn, but be sure to keep it solid so you don't have to worry about writing a program. I prepared a table and a figure as before.
transition | Description |
---|---|
activate(activation) | Execute the prepare function that collects the materials necessary for execution |
disable(Invalidation) | Change the value of the variable that stores the state to disabled |
enable(activation) | Change the value of the variable that stores the state to ready |
start(start) | Execute main function that performs heavy processing such as machine learning and infinite loop |
complete(Done) | Execute shutdown function to release memory etc. |
suspend(Suspension) | 実行中のmain関数にSuspensionシグナルを送ります |
resume(Resume) | 中断中のmain関数にResumeシグナルを送ります |
terminate(forced termination) | Execute teardown function to release memory etc. |
New words such as prepare function and main function came out, Having these will make it easier to write the program.
As a concrete image, when creating each system by inheriting the original System class, You must always insert super () when overriding activate or start. (Because state changes and logging are performed at each transition) This is annoying, so you can solve it by letting each system-specific process go to another function such as prepare or main.
Although the title mentions machine learning, for the sake of simplicity, this time we will substitute the sleep function as a time-consuming process. First, create the original System class.
class System():
def __init__(self, name):
self.name = name
self.state = "initial"
self.kwargs = {}
self.log(self.state)
def log(self, msg):
date = datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f")
line = f"{date}\t[{self.name}]\tpid:{os.getpid():05d}\t{msg}"
print(line)
def prepare(self, **kwargs):
pass
def main(self):
pass
def activate(self):
self.prepare(**self.kwargs)
self.state = "ready"
self.log(self.state)
def start(self):
self.state = "running"
self.log(self.state)
self.main()
def complete(self):
self.state = "completed"
self.log(self.state)
def transit(self):
self.activate()
self.start()
self.complete()
async def run(self, **kwargs):
self.kwargs = kwargs
executor = ProcessPoolExecutor(max_workers=None)
loop = asyncio.get_event_loop()
await loop.run_in_executor(executor, self.transit)
It just runs sleep in parallel, so it doesn't implement all of the states and transitions introduced endlessly: sob :: pray:
The constructor \ _ \ _ init \ _ \ _ names this system and sets the initial state. In transit, the transition of the blue route is executed in order. When implementing disable or terminate I think that you can write beautifully if you put try-except in this part.
In the last run defined as the async function, run_in_executor allows transit to be treated as a coroutine function. Also, in prepare etc., arguments may be taken depending on the user, so as variable length arguments I'd like to pass it to transit and even active, but this run_inexecutor, in the case of multi-process I get an error when I try to pass a variadic argument. Since there is no way, it is stored in the instance variable kwargs.
Next, create a system that executes the "system that executes the sleep function". It's a confusing phrase, but if you want to run multiple systems, I want to avoid writing directly in \ _ \ _ main \ _ \ _, so I will create an appSystem as a wrap system.
class appSystem(System):
def prepare(self):
pass
def main(self):
sleep1 = sleepSystem("sleepSystem1")
sleep2 = sleepSystem("sleepSystem2")
systems = asyncio.gather(
sleep1.run(sleep=5),
sleep2.run(sleep=3)
)
loop = asyncio.get_event_loop()
loop.run_until_complete(systems)
Here, the meaning of separating the processes such as activet and prepare, start and main comes out. This time it's just sleep, so there's nothing special to write in prepare. ~~ You can forcibly write the variables stored in the instance ... ~~
Execute sleepSystem1 which sleeps for 5 seconds and sleepSystem2 which sleeps for 3 seconds in main. sleepSystem is a simple system like this:
class sleepSystem(System):
def prepare(self, sleep=3):
self.sleep = sleep
def main(self):
time.sleep(self.sleep)
After that, add appSystem.run () to the event loop with the main function. 13
def main():
app = appSystem("appSystem")
loop = asyncio.get_event_loop()
loop.run_until_complete(app.run())
if __name__ == "__main__":
main()
Let's run it.
2019-12-14 16:43:28.843830 [appSystem] pid:30360 initial
2019-12-14 16:43:29.196505 [appSystem] pid:21020 ready
2019-12-14 16:43:29.196505 [appSystem] pid:21020 running
2019-12-14 16:43:29.197501 [sleepSystem1] pid:21020 initial
2019-12-14 16:43:29.197501 [sleepSystem2] pid:21020 initial
2019-12-14 16:43:29.799470 [sleepSystem1] pid:29720 ready
2019-12-14 16:43:29.803496 [sleepSystem1] pid:29720 running
2019-12-14 16:43:29.872484 [sleepSystem2] pid:18868 ready
2019-12-14 16:43:29.872484 [sleepSystem2] pid:18868 running
2019-12-14 16:43:32.873678 [sleepSystem2] pid:18868 completed
2019-12-14 16:43:34.804446 [sleepSystem1] pid:29720 completed
2019-12-14 16:43:34.804446 [appSystem] pid:21020 completed
From left to right, the date, system name, PID, and status. The time when sleepSystem1 and sleepSystem2 entered the running state is almost the same time, In addition, they are separate processes and proceed at the same time, and after 3 to 5 seconds, the completed state transition, And you can see the completed appSystem.
Finally, I will post the entire program.
import asyncio
import time
from datetime import datetime
import os
from concurrent.futures import ProcessPoolExecutor
class System():
def __init__(self, name):
self.name = name
self.state = "initial"
self.kwargs = {}
self.log(self.state)
def log(self, msg):
date = datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f")
line = f"{date}\t[{self.name}]\tpid:{os.getpid():05d}\t{msg}"
print(line)
def prepare(self, **kwargs):
pass
def main(self):
pass
def activate(self):
self.prepare(**self.kwargs)
self.state = "ready"
self.log(self.state)
def start(self):
self.state = "running"
self.log(self.state)
self.main()
def complete(self):
self.state = "completed"
self.log(self.state)
def transit(self):
self.activate()
self.start()
self.complete()
async def run(self, **kwargs):
self.kwargs = kwargs
executor = ProcessPoolExecutor(max_workers=None)
loop = asyncio.get_event_loop()
await loop.run_in_executor(executor, self.transit)
class appSystem(System):
def prepare(self):
pass
def main(self):
sleep1 = sleepSystem("sleepSystem1")
sleep2 = sleepSystem("sleepSystem2")
systems = asyncio.gather(
sleep1.run(sleep=5),
sleep2.run(sleep=3)
)
loop = asyncio.get_event_loop()
loop.run_until_complete(systems)
class sleepSystem(System):
def prepare(self, sleep=3):
self.sleep = sleep
def main(self):
time.sleep(self.sleep)
def main():
app = appSystem("appSystem")
loop = asyncio.get_event_loop()
loop.run_until_complete(app.run())
if __name__ == "__main__":
main()
It was a rush, but I introduced an example of web application design for machine learning. The program example is only sleep, but it does not implement www server or machine learning. Since the idea itself is the same, I think that there is little time and effort. (~~ If you write too much concretely, it's a company, so it's considerably simplified ~~)
In addition, communication between systems is performed by basic WebSocket. It's a good idea to create a websocketSystem separate from the wwwSystem and make it a subsystem of the appSystem.
So how was it? I haven't used it for a long time yet, but I personally like it because of its beautiful design.
http://itdoc.hitachi.co.jp/manuals/3020/30203M8120/EM810359.HTM
Recommended Posts