ML@Edge Image Classifications example using MXNet pretrained model with GreenGrass Core running on a Rasperry Pi
This is an example of running Lambda on GreenGrass with MXNet pretrained model Inception v3 for image classification
Details on Inception v3 can be found in https://arxiv.org/abs/1512.00567
- Raspberry Pi 3 Model B+ set up and configured for use with AWS IoT Greengrass
- Raspberry Pi Camera Module V2 - 8 Megapixel, 1080p
(Details in https://docs.aws.amazon.com/greengrass/latest/developerguide/ml-console.html#ml-inference-prerequisites)
- Create a plain text file
wpa_supplicant.conf
in the root of the sd card with contents
ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
update_config=1
country=«your_ISO-3166-1_two-letter_country_code, e.g. CA»
network={
ssid="«your_SSID»"
psk="«your_PSK»"
key_mgmt=WPA-PSK
}
- Goto https://console.aws.amazon.com/iot/home?#/greengrass/grouphub
- Select
Create Group
- Select
Use easy creation
- Enter a Group Name, such as
local_image-greengrass-group
, and selectNext
- In the next page, accept the default core name
local_image-greengrass-group_Core
, and selectNext
- In the screen
Run a scripted easy Group creation
, selectCreate Group and Core
- In the screen
Connect your Core device
, chooseDownload these resources as a tar.gz
- Choose
Choose a root CA
, download the root CARSA 2048 bit key: Amazon Root CA 1
- We will be installing the Greengrass Core software using Snap so we can skip that step and choose
Finish
- From your local computer, copy the resources downloaded in Setting up Greengrass group and core to the Raspberry Pi, e.g.
$ scp <uid>-setup.tar.gz pi@<raspberry pi>:~/
and$ scp AmazonRootCA1.pem pi@<raspberry pi>:~/
- SSH into the Raspberry Pi, follow the steps in https://docs.aws.amazon.com/greengrass/latest/developerguide/module1.html#setup-filter.rpi to prepare the Raspberry Pi for GreenGrass
- Follow the steps in https://docs.aws.amazon.com/greengrass/latest/developerguide/what-is-gg.html#gg-downloads to install the GreenGrass core software
- Copy the certificates and config of the Greengrass core into the greengrass folder
$ sudo tar zxvf <uid>-setup.tar.gz -C /greengrass/ && sudo cp ~/AmazonRootCA1.pem /greengrass/certs/root.ca.pem
- Making sure the resources are in the correct folders, e.g.
$ ls /greengrass/certs/
and you should see your cert, private key, public key and Amazon CA certs in the folder - Starts the Greengrass software
$ sudo /greengrass/ggc/core/greengrassd start
- If the configuration is in place, you should see the GreenGrass core running message, such as
$ sudo /greengrass/ggc/core/greengrassd start
Setting up greengrass daemon
Validating hardlink/softlink protection
Waiting for up to 1m10s for Daemon to start
Greengrass successfully started with PID: <PID>
- You should also be able to see that the GreenGrass Core software successfully subscribed to the GreenGrass MQTT Topics
$ sudo tail /greengrass/ggc/var/log/system/runtime.log
[2019-05-10T11:01:02.078-07:00][DEBUG]-Entering OnConnect. {"clientId": "local_image-greengrass-group_Core"}
[2019-05-10T11:01:02.078-07:00][INFO]-MQTT connection connected. Start subscribing. {"clientId": "local_image-greengrass-group_Core"}
[2019-05-10T11:01:02.078-07:00][INFO]-Deployment agent connected to cloud.
[2019-05-10T11:01:02.078-07:00][DEBUG]-Subscribe retry configuration. {"IntervalInSeconds": 60}
[2019-05-10T11:01:02.078-07:00][INFO]-Start subscribing. {"numOfTopics": 2, "clientId": "local_image-greengrass-group_Core"}
[2019-05-10T11:01:02.079-07:00][INFO]-Trying to subscribe to topic $aws/things/local_image-greengrass-group_Core-gda/shadow/update/delta
[2019-05-10T11:01:02.194-07:00][DEBUG]-Subscribed to topic. {"topic": "$aws/things/local_image-greengrass-group_Core-gda/shadow/update/delta"}
[2019-05-10T11:01:02.194-07:00][INFO]-Trying to subscribe to topic $aws/things/local_image-greengrass-group_Core-gda/shadow/get/accepted
[2019-05-10T11:01:02.309-07:00][DEBUG]-Subscribed to topic. {"topic": "$aws/things/local_image-greengrass-group_Core-gda/shadow/get/accepted"}
[2019-05-10T11:01:02.417-07:00][INFO]-All topics subscribed. {"clientId": "local_image-greengrass-group_Core"}
- In the Raspberry Pi, install the MXNet Dependencies following the steps in https://docs.aws.amazon.com/greengrass/latest/developerguide/ml-console.html#install-mxnet#install-mxnet
Create a Lambda function and upload this repository as a zip file
- Goto https://console.aws.amazon.com/lambda/home
- Choose
Author from scratch
- Function name:
greengrassImageClassification
- Runtime:
Python 2.7
- Execution role: Create a new role from AWS policy templates
- Role name:
greenGrassLambdaRole
- Choose
Basic Lambda@Edge permissions
from the Policy templates - Choose
Create function
- In the Function code, upload this repository as a zip file. You need these 2 files
greengrass_main.py
,load_model.py
and the foldergreengrasssdk/
in the zip file - Lambda Handler:
greengrass_main.function_handler
- Choose
Save
- From the
Actions
, choosePublish new version
- From the
Version: 1
, choose1
in theVersions
- From the
Actions
, chooseCreate alias
- Enter a name for the version alias, such as
mlRpi
, and choose1
in the Version
- Goto https://console.aws.amazon.com/iot/home?#/greengrass/grouphub
- Choose the relevant GreenGrass group
- From the menu in the left hand side, select
Lambdas
,Add Lambda
- In the
Add a Lambda to your Greengrass Group
, chooseUse existing Lambda
- Choose
greengrassImageClassification
, chooseNext
- In the
Select a Lambda version
, choose the Alias, such asAlias: mlRpi
- Goto https://console.aws.amazon.com/iot/home?#/greengrass/grouphub
- Choose the relevant GreenGrass group
- From the menu in the left hand side, select
Lambdas
, choose the LambdagreengrassImageClassification
- Choose
Edit
- Memory limit: 96 MB
- Timeout: 15 Seconds
- Lambda lifecycle: Make this function long-lived and keep it running indefinitely
- Environment variables
- MXNET_ENGINE_TYPE: NaiveEngine
- Goto https://console.aws.amazon.com/iot/home?#/greengrass/grouphub
- Choose the relevant GreenGrass group
- From the menu in the left hand side, select
Resources
- In the Local, select
Add Local
- Create a local resource with the following parameters:
- Resource name: videoCoreSharedMemory
- Resource type: Device
- Device path: /dev/vcsm
- Group owner file access permission: Automatically add OS group permissions of the Linux group that owns the resource
- Lambda function affiliations: greengrassObjectClassification
- Specify the permission this Lambda will have to the resource.: Read and write access
- Create another local resource with the following parameters:
- Resource name: videoCoreInterface
- Resource type: Device
- Device path: /dev/vchiq
- Group owner file access permission: Automatically add OS group permissions of the Linux group that owns the resource
- Lambda function affiliations: greengrassObjectClassification
- Specify the permission this Lambda will have to the resource.: Read and write access
- In your local computer, download http://data.mxnet.io/models/imagenet/inception-bn/Inception-BN-symbol.json and http://data.mxnet.io/mxnet/models/imagenet/synset.txt
- Download http://data.mxnet.io/models/imagenet/inception-bn/Inception-BN-0126.params and rename to Inception-BN-0000.params
- zip these 3 files as
inception_bn.zip
- Goto https://console.aws.amazon.com/iot/home?#/greengrass/grouphub
- Choose the relevant GreenGrass group
- From the menu in the left hand side, select
Resources
- In the Local, select
Add Machine Learning
- Select
Add machine learning resource
- Create a machine learning resource with the following parameters:
- Resource name: Inception
- Model source: Upload a model in S3 (including models optimized through Deep Learning Compiler). Make sure you have the word
greengrass
in your bucket name - Model from S3: (Upload the inception_bn.zip)
- Local path: /greengrass-machine-learning/mxnet/inception_bn
- Lambda function affiliations:greengrassObjectClassification
- Specify the permission this Lambda will have to the resource.: Read-only access
- Goto https://console.aws.amazon.com/iot/home?#/greengrass/grouphub
- Choose the relevant GreenGrass group
- From the menu in the left hand side, select
Settings
- In the
Group role
, chooseAdd role
, choose an IAM role that allows principalgreengrass.amazonaws.com
to assume, such asGreengrass_ServiceRole
- If you are creating a new IAM role for GreenGrass, make sure you have these 2 managed policies to the GreenGrass Service Role:
CloudWatchLogsFullAccess
andAWSGreengrassResourceAccessRolePolicy
- If you are creating a new IAM role for GreenGrass, make sure you have these 2 managed policies to the GreenGrass Service Role:
- Goto https://console.aws.amazon.com/iot/home?#/greengrass/grouphub
- From the
Actions
drop down list, selectDeploy
- If this is your first deployment, in
Configure how Devices discover your Core
, chooseAutomatic Detection
- In
Grant permission to access other services
, chooseGrant permission
- You can monitor the logs locally
sudo tail -F /greengrass/ggc/var/log/system/runtime.log
- Goto https://console.aws.amazon.com/iot/home?#/test
- In the
Subscription topic
, enterhello/world
- If all configuration is correct, an MQTT message with topic "hello/world' should be seen in the AWS IoT Test client, such as
New Prediction: [(0.18657419, 'n02676566 acoustic guitar'), (0.14744462, 'n03929660 pick, plectrum, plectron'), (0.1250492, 'n02787622 banjo'), (0.049499936, 'n03271574 electric fan, blower'), (0.038708802, 'n03476684 hair slide')]
Should you need to deploy a new version of Lambda function, you need to update the alias to the version using command aws lambda update-alias --function-name greengrassImageClassification --name mlRpi --function-version 2
If successfully updated, you should see the response
{
"FunctionVersion": "2",
"AliasArn": "arn:aws:lambda:us-east-1:159000643575:function:greengrassImageClassification:mlRpi",
"Name": "mlRpi",
"Description": ""
}
Now that the alias mlRpi
is pointing to the version, you can proceed to deploy the GreenGrass
- Git clone or download this repository to the Raspberry Pi
- Install the python 2.7 dependencies
sudo apt-get install -y python-dev python-setuptools python-pip python-picamera
- We are still using python 2.7, so we have to install the opencv python library using
sudo apt-get install python-opencv
- Runs the local main python file
$ python2.7 local_main.py
If configuration is proper, a prediction should be shown, such as
[(0.18657419, 'n02676566 acoustic guitar'), (0.14744462, 'n03929660 pick, plectrum, plectron'), (0.1250492, 'n02787622 banjo'), (0.049499936, 'n03271574 electric fan, blower'), (0.038708802, 'n03476684 hair slide')]
- Option 1: Connect the Raspberry Pi to a HDMI display
- Option 2: Install a VNC server on the Raspberry Pi
For Option 2, a display manager is required for the VNC server. lxsession is one of the lightweight display manager.
Steps:
sudo apt-get install realvnc-vnc-server realvnc-vnc-viewer lxsession
sudo raspi-config
, goes toInterfacing Options
->VNC
->Yes
However, PiCamera display the image in native renderer, therefore, direct capture mode
of the RealVNC need to be enabled in order to view the image over VNC
- From the VNC windows, click on the VNC Connect icon at the lower right
- From the top right of the VNC Connect windows, click on the expanded menu -> options
- In the
Troubleshooting
, check on theEnable direct capture mode
In this section, we will simulate a local GGAD (Greengrass Aware Devices) that subscribe to the prediction messages from the Lambda running on the GGC, and simulate the local inference when there is no internet connectivity.
These steps is to demonstrate the offline capability of ML@Edge of the AWS GreenGrass. You can run the script in the local computer that is in the same network as the Raspberry Pi, to simulate a local device that connects to the GreenGrass core locally.
- Goto the correct region of https://console.aws.amazon.com/iot
- From the left hand side of menu, choose
GreenGrass
->Groups
, the correct GreenGrass Group,Devices
->Add Device
- In the
Add a Device
, chooseCreate New Device
, enter a thing name, such asggcLocalDevice
- In the
Set up security
, chooseUse Defaults
1-Click - Download the certificates into a local folder, such as
<THIS REPOSITORY>/localSubscriber/certs
. - Rename the certificate file
<device id>.cert.pem
ascert.pem
- Rename the certificate file
<device id>.private.key
asprivate.key
- Download the RSA 2048 bit key root CA certificate from https://docs.aws.amazon.com/iot/latest/developerguide/managing-device-certs.html#server-authentication, and save in the
<THIS REPOSITORY>/localSubscriber/certs
- Create a virtualenv,
$ virtualenv local
- Source the virtualenv,
$ source local/bin/activate
- Pull in all the requirements,
$ pip install -r requirements.txt
- Runs the Python file,
$ python2.7 subscriber.py
- If it runs successfully, you should see the prediction messages
2019-05-05 11:19:46,792 - AWSIoTPythonSDK.core - INFO - connecting to <GreenGrass Core local IP>:8883
2019-05-05 11:19:46,793 - AWSIoTPythonSDK.core.protocol.mqtt_core - INFO - MqttCore initialized
2019-05-05 11:19:46,793 - AWSIoTPythonSDK.core.protocol.mqtt_core - INFO - Client id: ggcLocalDevice
2019-05-05 11:19:46,793 - AWSIoTPythonSDK.core.protocol.mqtt_core - INFO - Protocol version: MQTTv3.1.1
2019-05-05 11:19:46,793 - AWSIoTPythonSDK.core.protocol.mqtt_core - INFO - Authentication type: TLSv1.2 certificate based Mutual Auth.
2019-05-05 11:19:46,793 - AWSIoTPythonSDK.core - INFO - Connecting with caPath: ./groupCerts/<GreenGrassn Group CA Cert>.crt, private: ./certs/private.key, certPath: ./certs/cert.pem
2019-05-05 11:19:46,793 - AWSIoTPythonSDK.core.protocol.mqtt_core - INFO - Configuring certificates...
2019-05-05 11:19:46,793 - AWSIoTPythonSDK.core.protocol.mqtt_core - INFO - Configuring endpoint...
2019-05-05 11:19:46,794 - AWSIoTPythonSDK.core.protocol.mqtt_core - INFO - Performing sync connect...
2019-05-05 11:19:46,794 - AWSIoTPythonSDK.core.protocol.mqtt_core - INFO - Performing async connect...
2019-05-05 11:19:46,794 - AWSIoTPythonSDK.core.protocol.mqtt_core - INFO - Keep-alive: 600.000000 sec
2019-05-05 11:19:47,017 - AWSIoTPythonSDK.core.protocol.mqtt_core - INFO - Performing sync subscribe...
2019-05-05 11:19:47,035 - AWSIoTPythonSDK.core - INFO - Connected to host <GreenGrass Core local IP>:8883
Received a new message:
New Prediction: [(0.5641716, 'n04099969 rocking chair, rocker'), (0.20946382, 'n03065424 coil, spiral, volute, whorl, helix'), (0.052156523, 'n03271574 electric fan, blower'), (0.023835631, 'n03201208 dining table, board'), (0.017949697, 'n04009552 projector')]
from topic:
hello/world
--------------
To simulate the offline mode, remove the internet route from the default route in Raspberry Pi
- Login to the Raspberry Pi
- Making sure the default route exists
# sudo route -v
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
default 192.168.1.1 0.0.0.0 UG 303 0 0 wlan0
192.168.1.0 0.0.0.0 255.255.255.0 U 303 0 0 wlan0
- If your route table uses default route for local addresses, add a local route
# sudo route add -net 192.168.1.0/24 gw 192.168.1.1
- Double check the route
# sudo route -v
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
default 192.168.1.1 0.0.0.0 UG 303 0 0 wlan0
192.168.1.0 192.168.1.1 255.255.255.0 UG 0 0 0 wlan0
192.168.1.0 0.0.0.0 255.255.255.0 U 303 0 0 wlan0
- Check that the internet route is working as expected
# ping amazonaws.com
PING aMaZoNaWs.com (207.171.166.22) 56(84) bytes of data.
64 bytes from 166-22.amazon.com (207.171.166.22): icmp_seq=1 ttl=232 time=111 ms
64 bytes from 166-22.amazon.com (207.171.166.22): icmp_seq=2 ttl=232 time=100 ms
64 bytes from 166-22.amazon.com (207.171.166.22): icmp_seq=3 ttl=232 time=99.8 ms
- Remove the default
# sudo route del default
- Your route table should now left with local routes
# sudo route -v
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
192.168.1.0 192.168.1.1 255.255.255.0 UG 0 0 0 wlan0
192.168.1.0 0.0.0.0 255.255.255.0 U 303 0 0 wlan0
- Internet route should be lost
# ping amazonaws.com
connect: Network is unreachable
- However, the local subscriber should still receiving the predictions, e.g.
Received a new message:
New Prediction: [(0.5417486, 'n04099969 rocking chair, rocker'), (0.2136172, 'n03065424 coil, spiral, volute, whorl, helix'), (0.043906394, 'n03271574 electric fan, blower'), (0.02837033, 'n03201208 dining table, board'), (0.023345523, 'n04009552 projector')]
from topic:
hello/world
--------------
- Greengrass Troubleshooting guides https://docs.aws.amazon.com/greengrass/latest/developerguide/gg-troubleshooting.html
- To check Lambda local execution, the logs can be tailed with command
sudo tail -F /greengrass/ggc/var/log/user/<AWS region>/<account id>/greengrassImageClassification.log
. Successful execution should have a log similar to the following
[2019-05-10T19:08:06.401-07:00][INFO]-Lambda.py:92,Invoking Lambda function "arn:aws:lambda:::function:GGRouter" with Greengrass Message "New Prediction: [(0.12387366, 'n04372370 switch, electric switch, electrical switch'), (0.063494414, 'n03929660 pick, plectrum, plectron'), (0.058057442, 'n04254120 soap dispenser'), (0.042390004, 'n04153751 screw'), (0.03408207, 'n15075141 toilet tissue, toilet paper, bathroom tissue')]"
[2019-05-10T19:08:06.405-07:00][INFO]-ipc_client.py:142,Posting work for function [arn:aws:lambda:::function:GGRouter] to http://localhost:8000/2016-11-01/functions/arn:aws:lambda:::function:GGRouter
[2019-05-10T19:08:06.723-07:00][INFO]-ipc_client.py:155,Work posted with invocation id [c5467c56-9121-4bd6-6eb6-a937d8d9bdfb]
[2019-05-10T19:08:06.728-07:00][INFO]-lambda_runtime.py:114,Running [arn:aws:lambda:us-east-1:159000643575:function:greengrassImageClassification:3]
[2019-05-10T19:08:06.73-07:00][INFO]-ipc_client.py:170,Getting work for function [arn:aws:lambda:us-east-1:159000643575:function:greengrassImageClassification:3] from http://localhost:8000/2016-11-01/functions/arn:aws:lambda:us-east-1:159000643575:function:greengrassImageClassification:3/work
[2019-05-10T19:08:15.31-07:00][INFO]-[(0.1262314, 'n04372370 switch, electric switch, electrical switch'), (0.06892826, 'n03929660 pick, plectrum, plectron'), (0.05506966, 'n04254120 soap dispenser'), (0.044870526, 'n04153751 screw'), (0.033377603, 'n15075141 toilet tissue, toilet paper, bathroom tissue')]
[2019-05-10T19:08:15.311-07:00][INFO]-IoTDataPlane.py:115,Publishing message on topic "hello/world" with Payload "New Prediction: [(0.1262314, 'n04372370 switch, electric switch, electrical switch'), (0.06892826, 'n03929660 pick, plectrum, plectron'), (0.05506966, 'n04254120 soap dispenser'), (0.044870526, 'n04153751 screw'), (0.033377603, 'n15075141 toilet tissue, toilet paper, bathroom tissue')]"