Categories
Algorithms

Linear vs Logistic Regression, all in Numpy

The two entry level machine learning algorithms , linear and logistic regression are quite easy to understand and provide a good way to practice coding the general machine learning pipeline, in their vectorized form. Namely,

  • Prepping the dataset, eg: removing outliers, adding features(polynomial multiples of existing features), normalization, feature scaling.
  • Implementing the learning algorithm function.
  • Calculating the loss through the chosen loss function and hypothesis.
  • Optimization algorithm – Update you model’s parameters depending on the loss and ground truth.
  • Maintaining a config file for the total training process.

An entry level toy dataset: pima indians diabetes dataset, has a target of one variable for each datapoint(a binary classification task of predicting whether a person has diabetes or not), will be used for the sake of this tutorial blog.

About the dataset

You can know all about the dataset on Kaggle. The dataset has around 768 datapoints and 8 features, which is quite a sweet spot for having a decent model without worrying about underfitting. The data is about female patients, specifically, their BMI, insulin level, age, skin thickness, glucose level etc. The target tells if the person has diabetes or not. 500 of all the datapoints are non-diabetic. 268 are diabetic. The data is not highly skewed so normal test accuracy should suffice(no need to find precision, recall and F1 score).

Helper Functions

For normalizing the dataset

This helps in normalizing the data and bringing them in a range of 0-1.

def scalify_min_max(np_dataframe):
    minimum_array=np.amin(np_dataframe,axis=0)
    maximum_array=np.amax(np_dataframe,axis=0)
    range_array = maximum_array-minimum_array

    scaled = (np_dataframe-minimum_array)/range_array
    return scaled

For calculating the accuracy

def accuracy_calculator(Y_out,Y):
    accuracy=np.sum(np.logical_not(np.logical_xor(Y_out,Y)))/Y.shape[0]
    true_positives=np.sum(np.logical_and(Y_out,Y))
    false_positives=np.sum(np.logical_and(Y_out,np.logical_not(Y)))
    false_negatives=np.sum(np.logical_and(np.logical_not(Y_out),Y))
    precision=true_positives/(true_positives+false_positives)
    recall=true_positives/(true_positives+false_negatives)
    print("Precision:",precision,".Recall:",recall)
    F1_score=precision*recall/(precision+recall)
    return [accuracy,precision,recall,F1_score]

For preparing the dataset – creating train/val/test splits

def pre_data_prep(filename,dest_fileloc):
    with open(filename,'rb') as f:
        gzip_fd=gzip.GzipFile(fileobj=f)
        next(gzip_fd)#Skip first row
        diabetes_df = loadtxt(gzip_fd,delimiter=',',dtype=np.float32)
    Y=diabetes_df[:,-1]
    scaled_diabetes_df = scalify_min_max(diabetes_df[:,:-1])
    concat_diabetes = np.concatenate((scaled_diabetes_df,np.array([Y]).T),axis=1)
    savetxt(dest_fileloc,concat_diabetes,delimiter=',')

def dataprep(fileloc,split):
    assert len(split) == 3
    assert sum(split) == 1
    diabetes_data = loadtxt(fileloc,delimiter=',',dtype=np.float32)
    Y=np.array([diabetes_data[:,-1]]).T
    classes = np.unique(Y)
    assert len(classes) == 2
    X=diabetes_data[:,:-1]
    data_size=X.shape[0]
    print(data_size,X.shape,Y.shape)

    split_size=int(split[0]*data_size)
    val_split=int(split[0]*data_size)
    X_train=X[:split_size]
    X_val=X[split_size:split_size+val_split]
    X_test=X[split_size+val_split:]
    Y_train=Y[:split_size]
    Y_val=Y[split_size:split_size+val_split]
    Y_test=Y[split_size+val_split:]
    return X_train,X_val,X_test,Y_train,Y_val,Y_test

Evaluation function

For for finding accuracy of learned model on the test dataset.

def evaluate(theta_params,X,Y=None,thresh=0.5):
    data_size=X.shape[0]
    X_extend=np.concatenate((np.ones((data_size,1)),X),axis=1)
    pred = np.greater(np.matmul(X_extend,theta_params),thresh)*1
    cost=np.sum(np.square(np.matmul(X_extend,theta_params)-Y))/(data_size*2)
    return pred,cost

Logistic Regression Function

def sigmoid_func(theta,X):
    retval = 1/(1+np.exp(-1*np.matmul(theta.T,X)))
    return retval
    
def logistic_regression(X,Y,learning_rate=0.001,num_iters=100,thresh=0.5,rand_seed=None):
    if rand_seed!=None:#For reproducible results
        np.random.seed(rand_seed)
    data_size = X.shape[0]
    theta_params=np.array([np.random.randn(X.shape[1]+1)]).T
    #Add bias column to X
    X_extend = np.concatenate((np.ones((data_size,1)),X),axis=1).T
    cost=[]#Keep track of cost after each iteration of learning
    for i in tqdm(range(num_iters),desc="Training.."):
        h_theta=sigmoid_func(theta_params,X_extend).T#mX1
        grad=np.matmul(X_extend,(h_theta-Y))/data_size#nXm*mX1=nX1
        theta_params=theta_params-learning_rate*grad
        cost.append(-1*np.sum(Y*np.log(h_theta)+(1-Y)*np.log(1-h_theta))/(data_size))
    final_pred = np.greater(np.matmul(X_extend.T,theta_params),thresh)*1
    accuracy=np.sum(np.logical_not(np.logical_xor(final_pred,Y)))/data_size
    cost=np.array(cost)
    return theta_params,accuracy,cost

Linear Regression Function

def linear_regression(X,Y,learning_rate=0.001,num_iters=100,thresh=0.5,rand_seed=None):
    if rand_seed!=None:
        np.random.seed(rand_seed)
    data_size = X.shape[0]
    #print(X.shape,Y.shape)
    theta_params=np.array([np.random.randn(X.shape[1]+1)]).T
    X_extend = np.concatenate((np.ones((data_size,1)),X),axis=1)
    cost=[]
    for i in tqdm(range(num_iters),desc="Training.."):
        theta_params=theta_params-learning_rate*np.matmul((np.matmul(theta_params.T,X_extend.T)-Y.T),X_extend).T/data_size
        cost.append(np.sum(np.square(np.matmul(X_extend,theta_params)-Y)[0])/(data_size*2))
    final_pred = np.greater(np.matmul(X_extend,theta_params),thresh)*1
    accuracy=np.sum(np.logical_not(np.logical_xor(final_pred,Y)))/data_size
    cost=np.array(cost)
    return theta_params,accuracy,cost

Runner functions for Linear and Logistic Regressions

#######################--------Linear RUNNER---------###############################
def regression_runner(fileloc,data_split_ratios,seed_values):
    X_train,X_val,X_test,Y_train,Y_val,Y_test = dataprep(fileloc,data_split_ratios)
    all_models=[]
    all_val_accuracies=[]
    random_seeds=seed_values
    num_iters=500
    x_axis=np.arange(num_iters)
    for i in range(len(random_seeds)):
        model,train_accuracy,cost=linear_regression(X_train,Y_train,rand_seed=random_seeds[i],num_iters=num_iters)
        print("Trial:",i,".Train Accuracy:",train_accuracy)
        all_models.append(model)
        plt.plot(x_axis,cost,label=str(random_seeds[i]))
        
        val_prediction,val_cost=evaluate(model,X_val,Y_val)
        accuracy_precision=accuracy_calculator(val_prediction,Y_val)
        all_val_accuracies.append(accuracy_precision[0])
        print("Validation Accuracy:",accuracy_precision)
        print("Validation Cost:",val_cost)

    #plt.legend()
    plt.title("Linear Regression")
    plt.xlabel('Number of iterations')
    plt.ylabel('Cost')
    plt.show()
    max_accuracy_idx=np.where(all_val_accuracies==np.amax(all_val_accuracies))[0][0]
    best_model=all_models[max_accuracy_idx]
    print(best_model.shape)
    #print(X_test.shape,Y_test.shape)
    test_pred,test_cost=evaluate(best_model,X_test,Y_test)
    print(test_pred.shape,print(test_cost))
    test_accuracy,test_precision,test_recall,test_f1=accuracy_calculator(test_pred,Y_test)
    print("Test accuracy:",test_accuracy,".Test cost:",test_cost)

#####################-------------LOGISTIC RUNNER--------------##########################
def logistic_runner(fileloc,data_split_ratios,seed_values):
    X_train,X_val,X_test,Y_train,Y_val,Y_test = dataprep(fileloc,data_split_ratios)
    all_models=[]
    all_val_accuracies=[]
    random_seeds=seed_values
    num_iters=1500
    x_axis=np.arange(num_iters)
    for i in range(10):
        model,train_accuracy,cost=logistic_regression(X_train,Y_train,rand_seed=random_seeds[i],num_iters=num_iters)
        print("Trial:",i,".Train Accuracy:",train_accuracy)
        all_models.append(model)
        plt.plot(x_axis,cost,label=str(random_seeds[i]))

        val_prediction,val_cost=evaluate(model,X_val,Y_val)
        accuracy_precision=accuracy_calculator(val_prediction,Y_val)
        all_val_accuracies.append(accuracy_precision[0])
        print("Validation Accuracy:",accuracy_precision)
        print("Validation Cost:",val_cost)
    #plt.legend()
    plt.title("Logistic Regression")
    plt.xlabel('Number of iterations')
    plt.ylabel('Cost')
    plt.show()
    max_accuracy_idx=np.where(all_val_accuracies==np.amax(all_val_accuracies))[0][0]
    best_model=all_models[max_accuracy_idx]

    test_pred,test_cost=evaluate(best_model,X_test,Y_test)
    #print(test_pred.shape,print(test_cost))
    test_accuracy,test_precision,test_recall,test_f1=accuracy_calculator(test_pred,Y_test)
    print("Test accuracy:",test_accuracy,".Test cost:",test_cost)

Training Curves

Note that each of the below two trainings was performed with 10 different values of initial theta. The initial value of theta effects the overall training performance. The best of the 10 was taken in consideration for the final evaluation on the test dataset.

linear regression training
logistic regression training

Test Accuracies

Linear Regression:
Test accuracy: 0.7068965517241379 .Test cost: 0.14745936729023856

Logistic Regression:
Test accuracy: 0.646551724137931 .Test cost: 0.2865915372479961

Assimilated code

Gist Link

Additional Notes

  • The above code does not use regularization.
  • It may appear that for a few curves training was stopped prematurely, but infact the test results were more near optimal for the above training parameters.
  • Although linear regression appears to be performing better for the above case it might give poorer results for other datasets.
  • Note that logistic regression took 3X as many iterations as linear regression to converge.
  • Initial model parameters are chosen randomly by varying the seed values. Initial model parameters(theta) effects the overall training performance, hence 10 such values were taken.

Advertisement
Categories
Algorithms

Fast One Hot Encoding using Numpy (No For Loops)

In a lot of artificial intelligence applications, specially in supervised learning classification problems, where the labels for each of the datapoints are available, we often have a one-dimensional array containing the classes of each of the datapoints. Depending upon the machine learning algorithm we are going to apply to our dataset for classification, we might need to one-hot encode our labels.

What is One Hot Encoding?

Say, we are given a labels array like,

>>> Y=numpy.randdom.randint(15,size=10).reshape(-1,1)
>>> Y
array([[12],
       [ 8],
       [ 4],
       [ 4],
       [11],
       [ 0],
       [10],
       [10],
       [ 1],
       [ 8]])

Where each row contains the class number of the correspoding row for the datapoint X. Note that, in our example there are 6 unique classes, namely, 0,1,4,8,10,11,12 .One hot encoding for the above array Y will be,

>>> one_hot(Y)
array([[0., 0., 0., 0., 0., 0., 1.],
       [0., 0., 0., 1., 0., 0., 0.],
       [0., 0., 1., 0., 0., 0., 0.],
       [0., 0., 1., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 1., 0.],
       [1., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 1., 0., 0.],
       [0., 0., 0., 0., 1., 0., 0.],
       [0., 1., 0., 0., 0., 0., 0.],
       [0., 0., 0., 1., 0., 0., 0.]])

Where in each row has only one column set to 1 representing the class to which the datapoint belongs. For example, the first row has the last column set to 1. Which represents the column of the class ’12’. Which would mean we would also require the class to column mapping along with the one hot encoding. Something like,

array([[ 0],
       [ 1],
       [ 4],
       [ 8],
       [10],
       [11],
       [12]])

Here the row number of the class represents it’s column in the one hot encoding. It is simply a sorted order of all unique classes in Y.

The easy solution to the easy problem(For loop)

def one_hot_for(Y):
    data_size=Y.shape[0]
    classes=np.unique(Y).reshape(-1,1)
    num_classes=classes.shape[0]

    one_hot=np.zeros((data_size,num_classes))
    for row in range(data_size):
        one_hot[row,np.where(classes==Y[row])[0]]=1

    return one_hot,classes

The hard solution to the easy problem(vector(ish))

def one_hot(Y):
    data_size=Y.shape[0]
    classes=np.unique(Y).reshape(-1,1)
    num_classes=classes.shape[0]

    class_mappings=np.arange(0,max(Y)+1)
    class_mappings[np.unique(classes)]=np.arange(num_classes)
    Y=class_mappings[Y]

    one_hot=np.zeros((data_size,num_classes))
    one_hot[np.arange(data_size).reshape(-1,1),Y.reshape(-1,1)]=1
    class_col=np.sort(classes)
    return one_hot,class_col

Speed comparison

Generating random labels and storing in a file

import random

file_name="randoms.txt"
with open(file_name,"w+") as random_labels:
    for i in range(10000):
        random_labels.write(str(random.randint(0,1000))+"\n")

The above code will generate 10,000 random numbers and store them on individual lines in the file randoms.txt

Script for comparing the two functions

import matplotlib.pyplot as plt
import time
import numpy as np
from helper_functions import one_hot,one_hot_for

filename= "randoms.txt"

with open(filename,"r+") as f:
    Y=f.readlines()
    int_map=map(int,Y)
    Y=list(int_map)
    Y=np.asarray(Y).reshape(-1,1)

one_hot_timings=[]
one_hot_for_timings=[]
for i in range(100,10000,100):
    start=time.time()
    _,_=one_hot(Y[:i])
    end=time.time()
    one_hot_timings.append(end-start)

    start=time.time()
    _,_=one_hot_for(Y[:i])
    end=time.time()
    one_hot_for_timings.append(end-start)

plt.plot(one_hot_timings,label="one_hot_vector")
plt.plot(one_hot_for_timings,label="one_hot_for")
plt.xlabel('data_size for every 100 datapoints')
plt.ylabel('time of execution')
plt.legend(loc='best')
plt.show()

Result

one hot encoding algorithm execution time comparison
Comparison for time of execution for different one-hot encoding algos

Additional notes

  • Libraries such as scipy, torch, sklearn etc, could probably do this faster.
  • Depending on how often you call the above functions in your applications, it might not be relevant for you to choose between the above two methods as both need less than a few seconds at most.
  • The assimilated code for the above can be found here.

Categories
Electronics

DIY Raspberry Pi Power Hat

A Power Hat for raspberry pi is a great solution if your project needs to be portable. There are ready-made solutions available on the market but they can be quite costly, some even as costly as the pi itself. In this blog we will make a cheap version of a raspberry pi power hat.

Ingredients for the Pie Power Hat

  • A TP4056 board – $1/₹75
  • 2X40 female header pin(long) < $1/₹75
  • Switch, JST socket and wires < $1/₹75
  • Prototype board 60X55mm < $1/₹75
  • A 3.7-4.2V lithium ion battery < $6/₹500
power hat parts
Parts for the PI Power Hat

Note that the TP4056 has the rating 5V and 1A. However, the underpowered sign might still show up on the raspberry pi. Make sure that you don’t put a lot of load on the PI GPIOs. You might end up frying the TP4056. It is possible that you might not find a 2X40 (long)female header pin in your local market(For some reason they are always out of stock), if you decide on using a 10X1(long) headers instead, make sure that you sand down the header on one side(not too much or the metal part of header might fall off). You can also use a smaller prototype board if your batter is relatively smaller and you are confident you won’t have any issues soldering on a smaller board.

Cooking Instructions

  1. Solder the header pin to the board. For this, solder two pins at each end of the header box. Check whether the header box is precisely perpendicular to the prototype board. If no then adjust while heating the solder points. If yes then solder the rest of the pins.
  2. On the other end of the prototype board solder a JST socket.

3. Between the JST socket and header pin box insert solder the TP4056 using it’s “IN+/IN-” pins. This will only to keep the TP4056 in place.
4. On the opposite side of the header pin solder a switch of your liking,(If I had a good small switch I would have used that instead). Just make sure you nothing peeks out on the other side of the proto board, as we don’t want anything sharp near the Lithium Ion Battery.
5 Solder connections using small wires according to the following diagram. For soldering wires to the header pins, flip the board and solder on the pin sides of the Power hat. Make sure all the wires are close to the prototype board and not sticking out as they might touch the components on the pi when you put the Hat on your raspberry pi.
6. (Optional) Solder male header pin to the board(along the board not through) for additional components that might need power. Like an audio module. Just make sure it doesn’t require a lot of current. connect these header pins to OUT+/- pins of TP4056.
7. (Optional) Make a housing for the battery on the board itself. If your application is going to go through a lot of wear and tear it is recommended that you create a housing for the battery aswell to keep it away from anything sharp.

pi power hat connections

Caution!

  1. Although TP4056 is rated 5V/1A, the Pi might still show that it is under-powered. If you don’t draw much current from the Pie You will do just fine. I used it with a waveshare knock off 3.2 LCD display+arduino pro micro usb gamepad just without any issues. But when I plug in a phone, the pi switches off.
  2. It would be good if your battery has an internal protection board to prevent over discharge. As you can see this doesn’t have any way of telling how much charge is left in the battery. So you might have to do some calculations on the rating of your battery and how long it lasts with your application.
  3. If you draw too much current the protection board on the battery or the TP4056 might get fried.
  4. When buying the TP4056 module do lookout for if the board has the “OUT” pins aswell. There are variants available which are only used to charge a battery.

Gallery

Categories
Electronics

DIY Wired Arduino Pro Micro Gamepad

This project utilizes the Pro Micro’s capability to appear as a HID compatible device to the system. This by no means is an expert project thanks to the awesome work already done by some incredible people. The ArduinoJoystick library made this project a breeze at the coding end of things. This is not a beginner’s project either as it involves some tricky soldering with the thumbslide joysticks and it uses all of the digital and analog pins that the pro micro has to offer.

Contents

What is a HID compatible device?

An HID device or Human Interface Device, in lay man’s terms is a peripheral device(eg. Keyboard, Mouse, Joystick, Gamepad) which can take inputs from the user. HID devices follow the USB standard, which means you don’t have to explicitly write a driver for the device on your system. HID devices will always work as plug-and play devices on most computers and mobile phones.

Thanks to the ArduinoJoystick Library we don’t have to worry about following the USB convention for marking the device as Gamepad, defining the number of buttons, axes, hat switches, defining collections and what not! We can directly get to the good stuff.

What do you need?

Everything that you need for this project is readily available in the market, so I will not link any stores. If this is your first intermediate difficulty project I would recommend you buy twice as many parts as you might mess up some of your parts or you might want to recreate a better version of your device later. Also, clones of original products are cheaper but are not very good quality either. However, bricking one such clone won’t be as bad as bricking an original for sure.

  • 6mm Tactile switches (with good caps if available) – 10pcs.
  • 6mm Right Angled Tactile Switches – 2 pcs.
  • PSP1000 compatible analog sticks – 2 pcs.
  • A Pro Micro (ATMEGA32U4) board (Note: ATMEGA328P boards do not have HID capability)
  • A Prototype board (Or you can get a PCB printed like I did, download my gerber files here).
  • Additionally, you might require solder, desoldering copper, pointed soldering tip, tape, wires, hot glue gun.

Soldering components and making connections

For the ease of mapping buttons to their respective pins I am attaching the picture of the PCB board.

PCB layout/Component Connections

Note that one end of each tactile pin is connected to a digital pin on the board and the other end is connected to Ground. For the analog thumbslide joysticks there are 4 pins, one for ground one for VCC, and the other two for each of the potentiometers inside the joystick, reporting two axes per joystick.

For the right angled tactile switches make sure you use the smaller pins for the connections and not the bigger ones.

It would be wise to not directly solder the Pro Micro to your prototype board. Try attaching female headers on the PCB and male headers on the pro-micro first. For soldering the thumb-slide joysticks apply some solder on each of it’s plates and the plates on the PCB first (make sure you use a pointed soldering tip for this). Then carefully place down the analog joystick such that none of the plates touch the adjacent plate. Apply tape or glue from glue gun to keep the analog stick in place. If you have screws of appropriate size then you may even screw the component to the board or alternatively pull wires from the holes to keep it in place. Flip over the board and for each of the plates find the corresponding holes and gently put some solder through it. Make sure you don’t put a lot of solder as it might spill on the other side of the board making a connection with the adjacent plate. Remove the wires and the glue once the soldering is done.

If you are having a hard time with soldering the analog sticks to the board, and you are using a prototype board instead of the PCB that I made, then you may consider using the more common type of analog stick, the PS2 analog stick. It has a larger footprint but is much easier to work with and give much lesser noise. You might have to desolder them from the board that they came on first. You will not be able to use the internal switch that these contain though as all the digital pins on the board are already in use (The TX and RX (pins 0 and 1) are for serial communication). Here’s the required wiring mapping for the PS2 analog stick.

PS2 analog stick footprint and connections

If you think analog sticks are giving you a hard time you may save them for your next project and just make the gamepad with 12 buttons. I will give codes for a few possible variants of the gamepad you may use the one which suits you best.

If you are using the PCB that I made you can test the connections using a multimeter.

When you are happy with your connections place the pro micro in the header slot and plug it to your system via USB. We are ready to program your gamepad.

Setting up the Arduino Joystick Library

Download the ArduinoJoystick Library Here.

Open your Arduino IDE.

Goto Sketch > Include Library > Add .ZIP Library…

Browse and select the zip folder you downloaded earlier.

Your Joystick library is all set!

Code Away!

CONFIG 1: L+R, POV hat(4 buttons), A, B, X, Y, Left Analog Stick(X and Y axes), Right Analog Stick(Rx and Ry axes)

#include <Joystick.h>
#define UP_PIN 5
#define DOWN_PIN 6
#define LEFT_PIN 4
#define RIGHT_PIN 7
#define X_KEY_PIN 10
#define Y_KEY_PIN 16
#define A_KEY_PIN 14
#define B_KEY_PIN 15
#define START_PIN 8
#define SELECT_PIN 9
#define L_KEY_PIN 3
#define R_KEY_PIN 2

Joystick_ Joystick(JOYSTICK_DEFAULT_REPORT_ID,JOYSTICK_TYPE_GAMEPAD,
  8, 1,                  // Button Count, Hat Switch Count
  true, true, false,     // X and Y, Z Axis
  true, true, false,   //  Rx, Ry, Rz
  false, false,          //  rudder, throttle
  false, false, false);  // accelerator, brake, steering

void setup() {
  // Initialize Button Pins
  pinMode(2, INPUT_PULLUP);
  pinMode(3, INPUT_PULLUP);
  pinMode(4, INPUT_PULLUP);
  pinMode(5, INPUT_PULLUP);
  pinMode(6, INPUT_PULLUP);
  pinMode(7, INPUT_PULLUP);
  pinMode(8, INPUT_PULLUP);
  pinMode(9, INPUT_PULLUP);
  pinMode(10, INPUT_PULLUP);
  pinMode(16, INPUT_PULLUP);
  pinMode(14, INPUT_PULLUP);
  pinMode(15, INPUT_PULLUP);
  //Analog pins don't need setup
  
  // Initialize Joystick Library
  Joystick.setXAxisRange(-127,127);
  Joystick.setYAxisRange(-127,127);
  Joystick.setRxAxisRange(-127,127);
  Joystick.setRyAxisRange(-127,127);
  Joystick.begin();
}

//Joy1
int xPosition = 0;
int yPosition = 0;
int mapX = 0;
int mapY = 0;
//Joy2
int xPosition1 = 0;
int yPosition1 = 0;
int mapX1 = 0;
int mapY1 = 0;

void loop() {

  // JOY1
  xPosition = analogRead(A0);
  delay(2);
 //Delay between reading analog inputs
  yPosition = analogRead(A1);
  delay(2);
  xPosition1 = analogRead(A2);
  delay(2);
  yPosition1 = analogRead(A3);
  delay(2);
  
  mapX = map(xPosition, 0, 1023, -127, 127);
  mapY = map(yPosition, 0, 1023, -127, 127);
  mapX1 = map(xPosition1, 0, 1023, -127, 127);
  mapY1 = map(yPosition1, 0, 1023, -127, 127);
  if (mapX>10)
  {Joystick.setXAxis(mapX);}
  else if (mapX<-10)
  {Joystick.setXAxis(mapX);}
  else
  {Joystick.setXAxis(0);}

  if (mapY >10)
  {Joystick.setYAxis(mapY);}
  else if (mapY < -10)
  {Joystick.setYAxis(mapY);}
  else
  {Joystick.setYAxis(0);}
  

  if (mapX1>10)
  {Joystick.setRxAxis(mapX1);}
  else if (mapX<-10)
  {Joystick.setRxAxis(mapX1);}
  else
  {Joystick.setRxAxis(0);}

  if (mapY1 >10)
  {Joystick.setRyAxis(mapY1);}
  else if (mapY < -10)
  {Joystick.setRyAxis(mapY1);}
  else
  {Joystick.setRyAxis(0);}


  //UP
  if (digitalRead(UP_PIN) == LOW)
  {Joystick.setHatSwitch(0,0);}
  //DOWN
  else if (digitalRead(DOWN_PIN) == LOW)
  {Joystick.setHatSwitch(0,180);}
  //LEFT
  else if (digitalRead(LEFT_PIN) == LOW)
  {Joystick.setHatSwitch(0,270);}
  //RIGHT
  else if (digitalRead(RIGHT_PIN) == LOW)
  {Joystick.setHatSwitch(0,90);}
  else
  {Joystick.setHatSwitch(0,-1);}

  // A_KEY
  if (digitalRead(A_KEY_PIN) == HIGH)
  {Joystick.setButton(0, LOW);}
  else
  {Joystick.setButton(0, HIGH);}

  // B_KEY
  if (digitalRead(B_KEY_PIN) == HIGH)
  {Joystick.setButton(1, LOW);}
  else
  {Joystick.setButton(1, HIGH);}

  // X_KEY
  if (digitalRead(X_KEY_PIN) == HIGH)
  {Joystick.setButton(2, LOW);}
  else
  {Joystick.setButton(2, HIGH);}

  // Y_KEY
  if (digitalRead(Y_KEY_PIN) == HIGH)
  {Joystick.setButton(3, LOW);}
  else
  {Joystick.setButton(3, HIGH);}
  
  // L_KEY_PIN
  if (digitalRead(L_KEY_PIN) == HIGH)
  {Joystick.setButton(4, LOW);}
  else
  {Joystick.setButton(4, HIGH);}

  // R_KEY_PIN
  if (digitalRead(R_KEY_PIN) == HIGH)
  {Joystick.setButton(5, LOW);}
  else
  {Joystick.setButton(5, HIGH);}

  // START
  if (digitalRead(START_PIN) == HIGH)
  {Joystick.setButton(7, LOW);}
  else
  {Joystick.setButton(7, HIGH);}

  // SELECT
  if (digitalRead(SELECT_PIN) == HIGH)
  {Joystick.setButton(6, LOW);}
  else
  {Joystick.setButton(6, HIGH);}

  delay(10);
   
}

Select the correct port for your device. Make sure that you select the correct configuration for your board(5V or 3V, 5V pro micro operates at 16MHz and 3V at 8MHz, if you upload a sketch for the incorrect one you might end up bricking your Microcontroller. To recover a bricked controller refer to the appendix) Upload the sketch and automatically windows will show that it’s setting up a new device.

Test out your gamepad in the windows gamepad tester. In your search bar type “Set up USB game controllers”.

Select your gamepad from the list of available gamepads. Mine is named Arduino Leonardo.

Available game controllers

Once selected click on properties and then go to the “Test” tab. You will see the following.

Gamepad testing

Press each of the buttons on your gamepad and see if there is a response on the tester. The UP/DOWN/LEFT/RIGHT (DPAD buttons) will give a response on the Point of View Hat(POV HAT). The rest of the buttons are mapped in the following way, A->0,B->1,X->2,Y->3,L->5,R->6,Select->7,Start->8.

The left analog stick is mapped to the X and Y axes and the Right analog stick is mapped to X and Y rotation axes. Check for the full motion on the analog sticks.

Following codes are for different variants of gamepads that you could perhaps make.

CONFIG 2: L+R, UP/DOWN/LEFT/RIGHT -> POV Hat, A,B,X,Y, Select, Start, No analog sticks

No Analog Sticks

#include <Joystick.h>
#define UP_PIN 5
#define DOWN_PIN 6
#define LEFT_PIN 4
#define RIGHT_PIN 7
#define X_KEY_PIN 10
#define Y_KEY_PIN 16
#define A_KEY_PIN 14
#define B_KEY_PIN 15
#define START_PIN 8
#define SELECT_PIN 9
#define L_KEY_PIN 3
#define R_KEY_PIN 2

Joystick_ Joystick(JOYSTICK_DEFAULT_REPORT_ID,JOYSTICK_TYPE_GAMEPAD,
  8, 1,                  // Button Count, Hat Switch Count
  false, false, false,     // X and Y, Z Axis
  false, false, false,   //  Rx, Ry, Rz
  false, false,          //  rudder, throttle
  false, false, false);  // accelerator, brake, steering

void setup() {
  // Initialize Button Pins
  pinMode(2, INPUT_PULLUP);
  pinMode(3, INPUT_PULLUP);
  pinMode(4, INPUT_PULLUP);
  pinMode(5, INPUT_PULLUP);
  pinMode(6, INPUT_PULLUP);
  pinMode(7, INPUT_PULLUP);
  pinMode(8, INPUT_PULLUP);
  pinMode(9, INPUT_PULLUP);
  pinMode(10, INPUT_PULLUP);
  pinMode(16, INPUT_PULLUP);
  pinMode(14, INPUT_PULLUP);
  pinMode(15, INPUT_PULLUP);
  //Analog pins don't need setup
  
  Joystick.begin();
}

void loop() {

  //UP
  if (digitalRead(UP_PIN) == LOW)
  {Joystick.setHatSwitch(0,0);}
  //DOWN
  else if (digitalRead(DOWN_PIN) == LOW)
  {Joystick.setHatSwitch(0,180);}
  //LEFT
  else if (digitalRead(LEFT_PIN) == LOW)
  {Joystick.setHatSwitch(0,270);}
  //RIGHT
  else if (digitalRead(RIGHT_PIN) == LOW)
  {Joystick.setHatSwitch(0,90);}
  else
  {Joystick.setHatSwitch(0,-1);}

  // A_KEY
  if (digitalRead(A_KEY_PIN) == HIGH)
  {Joystick.setButton(0, LOW);}
  else
  {Joystick.setButton(0, HIGH);}

  // B_KEY
  if (digitalRead(B_KEY_PIN) == HIGH)
  {Joystick.setButton(1, LOW);}
  else
  {Joystick.setButton(1, HIGH);}

  // X_KEY
  if (digitalRead(X_KEY_PIN) == HIGH)
  {Joystick.setButton(2, LOW);}
  else
  {Joystick.setButton(2, HIGH);}

  // Y_KEY
  if (digitalRead(Y_KEY_PIN) == HIGH)
  {Joystick.setButton(3, LOW);}
  else
  {Joystick.setButton(3, HIGH);}
  
  // L_KEY_PIN
  if (digitalRead(L_KEY_PIN) == HIGH)
  {Joystick.setButton(4, LOW);}
  else
  {Joystick.setButton(4, HIGH);}

  // R_KEY_PIN
  if (digitalRead(R_KEY_PIN) == HIGH)
  {Joystick.setButton(5, LOW);}
  else
  {Joystick.setButton(5, HIGH);}

  // START
  if (digitalRead(START_PIN) == HIGH)
  {Joystick.setButton(7, LOW);}
  else
  {Joystick.setButton(7, HIGH);}

  // SELECT
  if (digitalRead(SELECT_PIN) == HIGH)
  {Joystick.setButton(6, LOW);}
  else
  {Joystick.setButton(6, HIGH);}

  delay(10);
   
}

CONFIG 3: L+R, UP/DOWN/LEFT/RIGHT->XY axes, A, B, X, Y, Select, Start, No analog sticks

DPAD mapped to X,Y axes
#include <Joystick.h>
#define UP_PIN 5
#define DOWN_PIN 6
#define LEFT_PIN 4
#define RIGHT_PIN 7
#define X_KEY_PIN 10
#define Y_KEY_PIN 16
#define A_KEY_PIN 14
#define B_KEY_PIN 15
#define START_PIN 8
#define SELECT_PIN 9
#define L_KEY_PIN 3
#define R_KEY_PIN 2

Joystick_ Joystick(JOYSTICK_DEFAULT_REPORT_ID,JOYSTICK_TYPE_GAMEPAD,
  8, 0,                  // Button Count, Hat Switch Count
  true, true, false,     // X and Y, Z Axis
  false, false, false,   //  Rx, Ry, Rz
  false, false,          //  rudder, throttle
  false, false, false);  // accelerator, brake, steering

void setup() {
  // Initialize Button Pins
  pinMode(2, INPUT_PULLUP);
  pinMode(3, INPUT_PULLUP);
  pinMode(4, INPUT_PULLUP);
  pinMode(5, INPUT_PULLUP);
  pinMode(6, INPUT_PULLUP);
  pinMode(7, INPUT_PULLUP);
  pinMode(8, INPUT_PULLUP);
  pinMode(9, INPUT_PULLUP);
  pinMode(10, INPUT_PULLUP);
  pinMode(16, INPUT_PULLUP);
  pinMode(14, INPUT_PULLUP);
  pinMode(15, INPUT_PULLUP);
  //Analog pins don't need setup
  
  // Initialize Joystick Library
  Joystick.setXAxisRange(-1,1);
  Joystick.setYAxisRange(-1,1);
  Joystick.begin();
}

void loop() {
  //UP  
  if (digitalRead(UP_PIN) == LOW)
  {Joystick.setYAxis(-1);}
  if (digitalRead(DOWN_PIN) == LOW)//DOWN
  {Joystick.setYAxis(1);}
  if ((digitalRead(UP_PIN) == HIGH)&&(digitalRead(DOWN_PIN) == HIGH))
  {Joystick.setYAxis(0);}

  if (digitalRead(RIGHT_PIN) == LOW)//RIGHT
  {Joystick.setXAxis(1);}
  if (digitalRead(LEFT_PIN) == LOW)//LEFT
  {Joystick.setXAxis(-1);}
  if ((digitalRead(RIGHT_PIN) == HIGH)&&(digitalRead(LEFT_PIN) == HIGH))
  {Joystick.setXAxis(0);}

  // A_KEY
  if (digitalRead(A_KEY_PIN) == HIGH)
  {Joystick.setButton(0, LOW);}
  else
  {Joystick.setButton(0, HIGH);}

  // B_KEY
  if (digitalRead(B_KEY_PIN) == HIGH)
  {Joystick.setButton(1, LOW);}
  else
  {Joystick.setButton(1, HIGH);}

  // X_KEY
  if (digitalRead(X_KEY_PIN) == HIGH)
  {Joystick.setButton(2, LOW);}
  else
  {Joystick.setButton(2, HIGH);}

  // Y_KEY
  if (digitalRead(Y_KEY_PIN) == HIGH)
  {Joystick.setButton(3, LOW);}
  else
  {Joystick.setButton(3, HIGH);}
  
  // L_KEY_PIN
  if (digitalRead(L_KEY_PIN) == HIGH)
  {Joystick.setButton(4, LOW);}
  else
  {Joystick.setButton(4, HIGH);}

  // R_KEY_PIN
  if (digitalRead(R_KEY_PIN) == HIGH)
  {Joystick.setButton(5, LOW);}
  else
  {Joystick.setButton(5, HIGH);}

  // START
  if (digitalRead(START_PIN) == HIGH)
  {Joystick.setButton(7, LOW);}
  else
  {Joystick.setButton(7, HIGH);}

  // SELECT
  if (digitalRead(SELECT_PIN) == HIGH)
  {Joystick.setButton(6, LOW);}
  else
  {Joystick.setButton(6, HIGH);}

  delay(10);
   
}

Congratulations you just made your very first plug-and-play gamepad! You are awesome! Don’t just sit there ideally show them FPS, platformers and RPGs what your new gamepad can do! All my codes and gerber files for PCB can be found on my GitHub repository.

What’s Next?

A wired gamepad is good, but not very portable. How about we make a HID compatible bluetooth gamepad next? You might have noticed that I have given room for HC-05 bluetooth module on my PCB. Maybe I will flash the RN42 firmware on the HC05 module and see where it goes. Or maybe I will simply modify the “class of device” my HC-05 using AT commands so that it’s recognized as a Gamepad 😉 . Or maybe I will end up bricking it irreversibly. I guess we’ll find out…

A new PCB with PS2 Analog sticks in the making

Appendix

1.Unable to upload sketch, Pro Micro not showing on COM port, Bricked my Arduino Pro Micro 😦

A. Try resetting your module

While your Pro Micro is plugged to your system via the USB, attach a wire from the ground pin to the RST pin twice in succession quickly. You will now have a 7-8 sec interval to upload a blank sketch to your board. If your system is slow you might want to press the upload button first then reset as the code is first compiled.

B. Reflash a new firmware to your Pro Micro, using another arduino as an ISP programmer

  1. Go to File > Examples > ArduinoISP > ArduinoISP, a sketch will open
  2. Upload this sketch to the working arduino
  3. Make note of the pins PIN_MOSI, PIN_MISO, PIN_SCK, RESET in the sketch. Connect wires to these pins of the working arduino. If some pins are not there change the pin number to available ones.
  4. Make connections from the working arduino to bricked Pro Micro such that the above noted pins connect

5. Go to Tools > Programmer > select “Arduino as ISP” if the working arduino has ATmega328p or select “Arduino as ISP(ATmega32U4)” if it has ATmega32u4

6. In Board: Select “Arduino Leonardo” or whichever board is compatible with you microcontroller. Select the correct port. Then Tools > Burn Bootloader. This will reflash the selected microcontroller’s firmware.

Arduino Joystick Library Basics

In truth most of the heavy lifting of the code is done in this part,

Joystick_ Joystick(JOYSTICK_DEFAULT_REPORT_ID,JOYSTICK_TYPE_GAMEPAD,
  8, 1,                  // Button Count, Hat Switch Count
  false, false, false,     // X and Y, Z Axis
  false, false, false,   //  Rx, Ry, Rz
  false, false,          //  rudder, throttle
  false, false, false);  // accelerator, brake, steering

Here you simply declare the number of buttons, Hat Switches, X,Y,Z, Rx, Ry, Rz, Rudder, Throttle, accelerator, brake, steering. Which are all more than enough to handle most of the use cases. Some of them overlap though so keep that in mind when making something bizzare. When taking input of axes on digital buttons we declare the range of the axes between -1 to 1. Where 1 represents infinity on and -1 -infinity of the axis. When a button is pressed you don’t check for analog inputs you simply map the button press to one of the extremes of the axis. Otherwise you set the axis to zero.

  //UP  
  if (digitalRead(UP_PIN) == LOW)
  {Joystick.setYAxis(-1);}
  if (digitalRead(DOWN_PIN) == LOW)//DOWN
  {Joystick.setYAxis(1);}
  if ((digitalRead(UP_PIN) == HIGH)&&(digitalRead(DOWN_PIN) == HIGH))
  {Joystick.setYAxis(0);}

Setup with Retropie

Plug in the gamepad to Pi. Press <ENTER>. Go to “Configure Input”, press a few buttons. If it says 1 gamepad detected, just long press any button and follow this instructions for setup. If it says 0 gamepad detected, then follow these instructions

  1. Press <SHIFT> + <F4> for opening up the terminal.
  2. type lsusb , this will show the list of plugged in devices. My device showed up as arduino leonardo. Note down the two numbers <VID>:<PID>
  3. then type, cd /etc/udev/rules.d
  4. then type, sudo nano new.rules , to open up the nano editor for editing the new file new.rules
  5. In the file add the following line, SUBSYSTEM=="input", ATTRS{idVendor}=="<VID>", ATTRS{idProduct}=="<PID>" ENV{ID_INPUT_JOYSTICK}="1" , replacing <VID> and <PID> with the numbers you noted earlier.
  6. Press <CTRL>+X then Y when prompted, to exit the nano editor. Do sudo reboot to restart your device. Goto “Configure Input” your device will now be detected.

Categories
Algorithms

Image Labelling tool for annotating object detection models

Create or modify your bounding boxes and annotations using this image labelling tool made using Python and tkinter. If you have a custom json you can modify the existing code according to your own needs.

Link to github

$ python gui.py

Categories
Algorithms

(Slightly) Non-Trivial Implementation of CLI Hangman. A Hands-on Python Tutorial.

Bored of learning python the regular way? Make a command line interface Hangman game and learn while you’re at it.

First try out the game for yourself! Run the following in your terminal,

pip install hangman-ultimate
hangman-ultimate

Never played the chalk board game of Hangman before? Rules are simple, you have to guess the name of a random animal letter by letter. Every wrong guess of a letter will bring the man closer to hanging. In retrospection, the game is a little dark for me to have played as a kid.

Alternatively, you can download the code from here.

Remember to like this post and start my Repository on GitHub if this post helped you!

Tear apart the code and make sure you have learnt the following at the end of it,
1. Opening a file and reading from it line by line.
2. Making multi-line strings.
3. Python Classes and Class instances(objects).
4. Class fields(variables) and methods(functions).
5. Passing class objects to functions.
6. Shared and non-shared fields(variables) amongst objects of same class.
7. Calling system(terminal) commands via the python script.
8. Catching an error using try except block.
9. Making basic command line animations.
10. Making programs compatible with both python 2 and python 3.
11. Pip packaging your python programs!

Got more ideas for CLI based games? Put them in the comments below!

Categories
Algorithms

Custom ASCII art and random one-liner welcome messages on Linux Terminal start-up

Would it not be cool if your terminal welcomes you with an ASCII art and a random one-liner each time you start it up!

Like this:-

Custom welcome to terminal

Remember to like my post and star my GitHub repository if this post helps you!

Linux users (Sorry Mac owners (for the time being, use the custom method after this)) can simply install my poketerm pip package!

Remember to install with sudo permissions!

$ sudo pip install poketerm
$ poketerm -t 1
$ poketerm -h

You can customise the above according to your need or you can save yourself some trouble and use my code on github which does everything that I am going to describe below.

Firstly download and put this file in your /home/$USER/ folder.

All you need to do now is manipulate the .bashrc shell script that Bash(Terminal) runs whenever it is started interactively. You can put any command in that file that you could type at the command prompt and they will run whenever you start Bash.

Note: The following makes changes to default terminal Bash. If you are using some other shell you will have to make changes to their respective analogous file.

NOTE FOR MAC USERS: The .bashrc equivalent of Mac is .bash_profile, Mac users may use that file for the following changes.

After opening this file with your favourite editor with a command like

vim ~/.bashrc

You will find the line containing the following in the start:

#!/bin/bash

Just after which you will have to add this small piece of code that I wrote:

echo "Welcome $USER "
echo "|\_                  _"
echo " \ \               _/_|"
echo "  \ \_          __/ /"
echo "   \  \________/   /"
echo "    |              |"
echo "    /              |"
echo "   |   0       0   |"
echo "   |       _       |"
echo "   |()    __    () |"
echo "    \    (__)      |"
file="/home/$USER/fortunes.txt"
if [ -f "$file" ]
then
    shuf -n 1 $file
fi

The file should look similar to this now:

.bashrc code after addition of lines

Save it and you are good to go!

Most of the above code is self explanatory. The only thing that you might not get is the statement inside the if conditional. If the file exists, then a random line is returned out of the 10,000 lines in the fortunes.txt file.

What more can you do?

You can create your own custom ASCII art or you could find some here. Just format it and replace it with my noobish art. Do keep in mind to not use art which is very large in line length/height else it might not appear as intended. Also keep in mind the characters that are used in the art. Test the piece of code as a separate shell script before you add it to the file. You can also save the art in a file/folder and invoke one randomly just like the one-liner.

Categories
Algorithms

When is the Best Sorting Algorithm the best?

There are many a problems in the real world and to each problem, a solution. In computer science, this solution is referred to as an algorithm. However, to a single problem, there often are many a algorithms to achieve the required result. For finding the best solution, we do time complexity analysis(here on referred to as TCA).

For those of you who know the basics of TCA, would know that we select the algorithm which performs best for a very large input size. For example, given below is the graph depicting the run-time of two algorithms A and B  for increasing input sizes.

example

According to the conventional TCA, the algorithm A is better than the algorithm B, as for large input sizes the average time of execution is less for the algorithm A.

The algorithm A, as we can see, has a linear time complexity, O(n), where as its quadratic for algorithm B, O(n²). In general, in TCA we consider an O(n) algorithm better than a O(n²) algorithm, as for a very large input size (an input size tending to infinity actually), a O(n) algorithm will work better than a O(n²) algorithm.

Now consider the following example,example3

Here again according to TCA, the algorithm C is better than the algorithm D. However, notice that for input sizes less than 10,000 , the algorithm D performed better than the algorithm C.

Note that, an input size of 10,000 is quite big, and might be the actual input size of the problem. With the conventional TCA we would have used the algorithm C for our practical application, when it clearly should have been better to use the algorithm D.

A very common problem in computers, is that of sorting. That is given an input array of numbers, give an output array with increasing/decreasing order of the elements of the input array. For example,

Input: [7,3,8,4,2,5,1,6,9,0]

Output: [0,1,2,3,4,5,6,7,8,9]

There are as we know, quite a few solutions to this problem, you can check out these algorithms here.

Five most common sorting algorithms are

  1. Bubble Sort
  2. Insertion Sort
  3. Selection Sort
  4. Quick Sort
  5. Merge Sort

The time complexities of the above algorithms are as follows,

table_low

We know that quick sort is the best sorting algorithm among all the above five algorithms. For big input sizes, you may checkout this comparison.

However, the question that we are trying to address here, is that after what input size is quick sort the best, and which is the best sorting algorithm for input sizes less than that input size?

I wrote a python code which plots the variations of average time of execution (averaged for 1000 inputs of the same size) for various input sizes. I took the actual time of execution on my machine rather than the counts as this is exercise is for finding the most practical solution.

big_size3

The reason for a lot of noise on in the graph are, the operating system overheads. It is to be noted that, I closed as many applications and background processes as I could. 

In spite of the noise, we still are able to make out that for a large input size what is the order or preference for sorting algorithms.

Quick > Merge > Insertion > Selection > Bubble

I ran multiple runs of the above code for small input sizes as well,

Here we notice that for input sizes <40 insertion sort performs the best out of all the above algorithms. So if you are working on some application which requires sorting of less than input sizes 40, you might want to consider using insertion sort rather than quick sort!

The code that I used for the above analysis can be found in the link,

https://github.com/devarshi16/sorting_comparison

Remember to like this post and start my GitHub repository if this post helped you!

Note that, you need to close as many applications as you can before the execution of the code. Once the code is running do not disturb the computer as it will raise additional overheads. If possible try to run the code while the system is offline.

There are still a lot of things that you can do with this code. Two such things are,

  1. Add more sorting algorithms for comparison from the plethora of sorting algorithms available here
  2. If you look carefully, you will see that merge sort performs horribly for small input sizes (probably attributed to the fact that it uses extra space for sorting). However, for big input sizes, it is the second best sorting algorithm. When does that happen? Try to find out!

Feel free to comment your opinions and pointing out errors. Thanks!