PysimpleGUI Calc not using function properly - python-3.x

Hey everybody really scratching my head trying to figure out what is wrong with my code, it does not return any errors it just seems to skip over the calculating portion and prints the error message I included to indicate wrong user input, even though my user inputs are valid.
I've tried moving the if and defining statements about the variables around to see if that would work.
I know the variables are defined first above the calculate function and again in the function, the reason I have it this way is because otherwise I would get an error that they were undefined unless they came first above everything else.
#Weight Converter Calculator with GUI
import PySimpleGUI as sg
def h():
h = height
def g():
g = diameter
if g == 9:
diameter = .7
if g == 11:
diameter == .4
if g == 11.5:
diameter = .38
def u():
u = typeunit
if u == ft:
typeunit = 1
if u == rl:
typeunit = 50
if u == pl:
typeunit = 450
def a():
a = amount
def calc_weight(h, g, u, a):
try:
h, g, u, a = float(h), float(g), float(u), float(a)
h = height
a = amount
g = diameter
u = typeunit
if g == 9:
g = .7
if g == 11:
g = .4
if g == 11.5:
g = .38
if u == 'ft':
u = 1
if u == 'rl':
u = 50
if u == 'pl':
u = 450
weight = h * g * u * a
if weight >= 47001:
standard = 'too heavy for a truck! '
elif weight <= 47000:
standard = 'will fit onto a truck! '
except (ValueError, ZeroDivisionError):
return None
else:
return f'Weight: {weight}, {standard}'
layout = [
[sg.Text('Please enter your desired Mesh Height, Gauge, Unit, Amount')],
[sg.Text('Mesh Height in FT', size =(15, 1)), sg.Input(key = h)],
[sg.Text('Gauge 9, 11, 11.5', size =(15, 1)), sg.Input(key = g)],
[sg.Text('Unit "ft" for sq ft, "rl" for roll, and "pl" for pallet', size =(15, 3)), sg.Input(key = u)],
[sg.Text('Amount', size =(15, 1)), sg.Input(key = a)],
[sg.Text('', key='weight', size=(20, 2))],
[sg.Submit(), sg.Cancel()]
]
window = sg.Window('Chain Link Weight Calculator', layout)
sg.theme('DarkAmber')
while True:
event, value = window.Read()
if event == 'Submit':
weight = calc_weight(value[h], value[g], value[u], value[a],)
if weight:
window.Element('weight').Update(weight, text_color='white')
else:
window.Element('weight').Update('Input is incorrect! ', text_color='red')
elif event == 'Cancel':
break
window.Close()

It looks like there're some issues
Using function name as key of element, like h, g, u and a and those function useless
Variables height, amount, diameter and typeunit not defined in function calc_weight
Variables h, g, u, a reset in function calc_weight
Just demo script here,
import PySimpleGUI as sg
def find_true(sequence):
return sequence.index(True)
def calc_weight(values):
try:
h, a = float(values['h']), float(values['a'])
index1 = find_true([values[key] for key in ('G1', 'G2', 'G3')])
index2 = find_true([values[key] for key in ('U1', 'U2', 'U3')])
g, u = guages[index1], units[index2]
weight = h*g*u*a
standard = 'too heavy for a truck!' if weight>= 47001 else 'will fit onto a truck!'
result = f'Weight: {weight}, {standard}'
except:
result = None
return result
guages = [0.7, 0.4, 0.38]
units = [1, 50, 450]
sg.theme('DarkAmber')
layout = [
[sg.Text('Please enter your desired Mesh Height, Gauge, Unit, Amount')],
[sg.Text('Mesh Height in FT', size =(15, 1)), sg.Input(key='h')],
[sg.Text('Gauge', size=(15, 1)),
sg.Radio("9", 'Guage', size=(8, 1), key='G1', default=True),
sg.Radio("11", 'Guage', size=(8, 1), key='G2'),
sg.Radio("11.5", 'Guage', size=(8, 1), key='G3'),],
[sg.Text('Unit', size=(15, 1)),
sg.Radio("sq ft", 'Unit', size=(8, 1), key='U1', default=True),
sg.Radio("roll", 'Unit', size=(8, 1), key='U2'),
sg.Radio("pallet", 'Unit', size=(8, 1), key='U3'),],
[sg.Text('Amount', size =(15, 1)), sg.Input(key='a')],
[sg.Text('', key='weight', size=(45, 2))],
[sg.Submit(), sg.Cancel()]
]
window = sg.Window('Chain Link Weight Calculator', layout)
while True:
event, values = window.Read()
if event in (sg.WINDOW_CLOSED, 'Cancel'):
break
elif event == 'Submit':
weight = calc_weight(values)
if weight:
window['weight'].update(weight, text_color='white')
else:
window['weight'].update('Input is incorrect!', text_color='white')
window.Close()

Related

DFS vs. Kruskal runtime (maze generation)

I have written two algorithms for creating unique mazes, one of them using depth-first-search (DFS) and the other using Kruskal's. The DFS algorithm performs as expected, however Kruskal's algorithm runs marginally slower than DFS and I do not know why.
I had written Kruskal's algorithm in Python.
I suspect the random.choice() function seems to be the underlying problem. The difference in runtime becomes noticeable when (r, c) > 30.
Here is the code for Kruskal's algorithm:
# Create a list of all possible edges
def create_edges(r, c):
edges = []
for y in range(r):
for x in range(c):
i = (y, x)
for d in ((0, 1), (0, -1), (1, 0), (-1, 0)):
p = tuple(map(sum, zip(d, i)))
py = p[0]
px = p[1]
if px in range(c) and py in range(r):
edges.append([i, p])
return edges
def kruskal(r, c, sz):
path = []
# Create a list of parent root nodes
roots = {(y, x) : [(y, x)] for y in range(r) for x in range(c)}
edges = create_edges(r, c)
while edges:
# Choose a random edge
edge = random.choice(edges)
parent = edge[0]
child = edge[1]
parent_set = get_set(roots, parent)
child_set = get_set(roots, child)
# Check if the parent / child are already in the same set
if parent_set == child_set:
rev_edge = edge.reverse()
if rev_edge in edges:
edges.remove(rev_edge)
edges.remove(edge)
continue
roots[parent_set] += roots[child_set]
roots.pop(child_set)
path.extend((parent, child))
rev_edge = edge.reverse()
if rev_edge in edges:
edges.remove(rev_edge)
edges.remove(edge)
return path
def get_set(roots, member):
s = None
for parent, children in roots.items():
if member in children:
s = parent
return s
def create_maze(t, r, c, sz):
maze = [['|_' for _ in range(c)] for _ in range(r)]
for cell in maze: cell.append('| ')
wd = {'DOWN' : ( 1, 0),
'UP' : (-1, 0),
'LEFT' : ( 0, -1),
'RIGHT': ( 0, 1)}
for n in range(len(t) - 1):
a = n
b = n + 1
p1 = t[a]
p2 = t[b]
ay, ax = p1[0], p1[1]
by, bx = p2[0], p2[1]
w = tuple(numpy.array(p2) - numpy.array(p1))
if w in wd.values():
k = list(wd.keys())[list(wd.values()).index(w)]
if k == 'DOWN': maze[ay][ax] = maze[ay][ax].replace('_', ' ')
if k == 'UP': maze[by][bx] = maze[by][bx].replace('_', ' ')
if k == 'LEFT': maze[ay][ax] = maze[ay][ax].replace('|', ' ')
if k == 'RIGHT': maze[by][bx] = maze[by][bx].replace('|', ' ')
return maze
def print_maze(maze, r, c, delay = 0):
s, l = min((r, c)), max((r, c))
a = 1 / (4 * r * c)
e = (1 / (s * l)) ** 2
delay = (a * 2.718 ** (-1 * e)) ** 0.5
time.sleep(delay)
print(' _' * c)
for iy in range(r):
for ix in range(c + 1):
print(maze[iy][ix], end = '')
print('')
print('')
def main():
r = 30
c = 30
sz = r * c
path = kruskal(r, c, sz)
maze = create_maze(path, r, c, sz)
print_maze(maze, r, c)
if __name__ == "__main__":
main()

Electric Vehicle Routing Path Simulation Giving Wrong Output

My electric vehicle charging algorithm aims at returning the shortest path length by a charging drone. The drone will meet the electric vehicle at a rendezvous point and charge the vehicle. The vehicle selection is done based on the charging urgency of the vehicles. The total path length is calculated when all vehicles are charged based on their urgency. The pseudocode is:
EDF(List_Req)
SunchrgEV = List_Req
Pathlen = 0
Nchgreq = 0
while(SunchrgEV = 0):
U obtains the updated location of all ei element of SunchrgEV via message exchange
S'gcs = set of GCS element of Sgcs
Gr = min {Eucleadian distance between Loc(ei) and Loc(Gj)} //The closest GCS
ex = min [{Eucleadian distance between Loc(ei) and Loc(Gr)}/ResidDist(ei)] // most urgent EV
if SoC(U)<RqB2D(ex) // then U(the drone) itself needs charge and it reports its termination to the server
// the server dispatches another U(drone) to charge the remaining EVs (ei)
Gr = min {Eucleadian distance between Loc(U) and Loc(Gj)} //The closest GCS to U where U goes to full charge
end if
t = tcurrent
// Finding rendezvous point where ex and U meets
RdvLoc = FindRdvLoc(ex, Loc(ex), Loc(U),t)
if RdvLoc is out of service area of U then
Report2server(ex,'Outofservicearea')
continue
end if
Pathlen += Dist2(Loc(U),RdvLoc)
U sends RdvLoc to ex and flies to RdvLoc
if U reaches ex in ChgRng(U) then
{Pathlen += Charge(ex, RdvLoc)
Nchgreq ++
}
else
Report2server(ex, 'Outofservicearea')
endif
SunchrgEV -= ex
do
Update (Loc(U))
Gr = min {Eucleadian distance between Loc(U) and Loc(Gj)} //U returns to Gr
Pathlen += Dist2(Loc(U), Loc(Gr))
return Pathlen and Nchgreq
//Compute the expected location where U meets e on Map(e)
def FindRdvLoc(e,Le,Lu,t):
Compute X = (x,y) on Rte(e,t) so that {Dist(e,X)/Speed(e,t)} = {Dist(U, X-ChgRng(U))/Speed(U)}
return X
def Charge(ei, RdvLoc):
U starts to charge ei and follows Rte(ei,t)
eLoc = FindLoc(Map(ei), Loc(ei), ChgTime(ei), Speed(ei,t))
return DistM(Map(ei), RdvLoc, eLoc)
The code that I have written so far gives the same output regardless of the input. The code is:
import math
import sys
from typing import List, Any
class Location:
x = 0
y = 0
def __init__(self, x, y):
self.x = x
self.y = y
class Result:
path_len: int = 0
n_charge_request: int = 0
def __init__(self, path_len, n_charge_request):
self.path_len = path_len
self.n_charge_request = n_charge_request
def to_string(self):
return "path len : " + str(self.path_len) + ", n_charge_request : " + str(self.n_charge_request)
def print(self):
print(self.to_string())
#SGCs
class ChargingStation:
def __init__(self, location: Location, fuel):
self.location = location
self.fuel = fuel
#EVs(ex)
class Vehicle:
location = Location(0, 0)
fuel = 1
isNeedEmergencyFuel = False
per_fuel_distance_travel = 2
def __init__(self, id, location, fuel):
self.id = id
self.location = location
self.fuel = fuel
# Resid (U)
def residual_distance(self):
return self.fuel * self.per_fuel_distance_travel # assuming each watt or kw fuel will yield to 2 killos or milies
# RqB2D
def requested_amount_of_charge(self, nearest_charge_station_location: Location) -> int:
distance = get_distance(self.location, nearest_charge_station_location)
cover = self.fuel * self.per_fuel_distance_travel
diff = math.fabs(distance - cover)
if diff > 0:
needed_fuel = diff / self.per_fuel_distance_travel + 2
return needed_fuel
return 0
# U(i)
class Drone:
location = Location(0, 0)
fuel = 0
isFlying = False
isSendForEvCharge = False
per_fuel_distance_travel = 20
serving_radius = 15
G = [
ChargingStation(Location(20, 10), 50),
ChargingStation(Location(50, 80), 40),
ChargingStation(Location(30, 30), 60)
]
def __init__(self, location, fuel):
self.location = location
self.fuel = fuel
# Resid (U)
def residual_distance(self):
return self.fuel * self.per_fuel_distance_travel # assuming each unit of fuel will yield to 2 kilos or miles
def is_out_of_service_zone(self, vehicle_location: Location): # ----->
distance = get_distance(self.location, vehicle_location)
return distance > self.serving_radius
#staticmethod
def get_distance(from_location, to_location):
x_dis = to_location.x - from_location.x
y_dis = to_location.y - from_location.y
x_dis_sqr = math.pow(x_dis, 2)
y_dis_sqr = math.pow(y_dis, 2)
final_dis_sum = x_dis_sqr + y_dis_sqr
final = math.sqrt(final_dis_sum)
return final
class EDFGenerator:
min_fuel = 50
charging_stations_for_drones = [ChargingStation(Location(2, 10), 80), ChargingStation(Location(2, 10), 50),
ChargingStation(Location(2, 10), 100)]
list_waiting_drones = [Drone(Location(5, 10), 50), Drone(Location(2, 10), 50), Drone(Location(2, 10), 50)]
list_sent_drones = []
list_charging_drones = []
def __init__(self):
pass
def rdv_loc(self, ei: Vehicle, drone_location: Location, t: int) -> Location | None:
needed_fuel = ei.requested_amount_of_charge(drone_location)
nearest_charge_station = self.get_nearest_charge_station(ei.location, self.charging_stations_for_drones)
needed_fuel_at_nearest_station = needed_fuel / ei.per_fuel_distance_travel
if nearest_charge_station.fuel < needed_fuel_at_nearest_station:
return None
else:
return nearest_charge_station.location
#staticmethod
def get_nearest_charge_station(from_location: Location, list_of_stations: List[ChargingStation]) -> ChargingStation:
nearest = list_of_stations[0]
min_distance = get_distance(from_location, nearest.location)
for station in list_of_stations:
dis = get_distance(from_location, station.location)
if min_distance > dis:
min_distance = dis
nearest = station
return nearest
def NChgReq(self) -> int:
charging_requesters = 0
for drone in self.list_waiting_drones:
if drone.isNeedEmergencyFuel:
charging_requesters += 1
return charging_requesters
def send_drone_to_charge(self, drone: Drone): # ----->
if drone.fuel < self.min_fuel:
nearest_station = self.get_nearest_charge_station(
drone.location,
self.charging_stations_for_drones)
self.list_sent_drones.append(drone)
self.list_waiting_drones.remove(drone)
drone.isSendForEvCharge = True
# send the drone to the charging station here
print(f"Drone {drone} sent to charging station at {nearest_station}")
def check_fuel_and_send_to_charge(self): # ----->
for drone in self.list_waiting_drones:
self.send_drone_to_charge(drone)
def get_drone(self, max_try=4) -> Drone:
if max_try <= 0:
print("max try failed for get_drone")
return None
# take one time from list_waiting_drones ------->
# add to list_sent_drones
# send or return the taken item
if len(self.list_waiting_drones) == 0:
return None
else:
# lastOne = self.list_waiting_drones.pop()
self.last_one = self.list_waiting_drones.pop()
if self.last_one.fuel < self.min_fuel:
print("low fuel failed to get_drone, retry")
self.list_waiting_drones.insert(0, self.last_one)
return max_try - 1
self.list_sent_drones.append(self.last_one)
return self.last_one
def get_closest_location_from_sending_server_to_e_vehicle(self, current_vechicle_location):
min_distance = sys.maxsize
for current_server in self.charging_stations_for_drones:
distance = get_distance(current_vechicle_location, current_server.location)
if min_distance > distance:
min_distance = distance
return min_distance
def get_most_urgent_electric_vehicle(self, closest_distance_between_server_ev: int,
all_uncharged_electric_vehicles: List[Vehicle]) -> Vehicle:
final_ev = None
min_distance = sys.maxsize
for ev in all_uncharged_electric_vehicles:
g_r = self.get_closest_location_from_sending_server_to_e_vehicle(ev.location)
residual_distance = ev.residual_distance()
eq = g_r / residual_distance
if min_distance > eq:
min_distance = eq
final_ev = ev
return final_ev
def reports_termination_to_server(self, drone: Drone):
self.list_charging_drones.append(drone)
self.charge_all_waiting_drones()
def charge_all_waiting_drones(self):
# assuming the environment is not async but synchronous
for drone in self.list_charging_drones:
drone.fuel = 100
self.list_waiting_drones.insert(0, drone)
self.list_charging_drones.clear()
#staticmethod
def report_to_server(ev: Vehicle, message):
print(ev.id + " - " + message)
def get_edf(self, list_req: List[Vehicle]) -> Result:
s_uncharged_electric_vehicles = list_req
path_len = 0
n_charge_req = 0
while len(s_uncharged_electric_vehicles) > 0:
print("uncharged_ev : " + str(len(s_uncharged_electric_vehicles)))
current_uncharged_ev = s_uncharged_electric_vehicles[0]
u = self.get_drone()
if u is None:
print("no drones from any station or with min charge")
return Result(path_len, n_charge_req)
# current_uncharged_ev.location aka e----->
e = current_uncharged_ev.location
# confusion SGCS what would be the SET
g_r: int = self.get_closest_location_from_sending_server_to_e_vehicle(
e) # closest vehicle from sending location
# confusion regarding dis (loc(e), g_r) , g_r already contains the distance
e_x = self.get_most_urgent_electric_vehicle(g_r, s_uncharged_electric_vehicles)
drone_residual_distance = u.residual_distance() # Resid (U)
ev_requested_amount_of_charge = e_x.requested_amount_of_charge(u.location) # RqB2D
if drone_residual_distance < ev_requested_amount_of_charge:
self.reports_termination_to_server(u)
u = self.get_drone()
g_r_2 = self.get_closest_location_from_sending_server_to_e_vehicle(
u.location) # closest vehicle from sending location
self.reports_termination_to_server(u) # sends back the drone for charging
# ?? t is something confusing, how to include t into the equation??
rdv_loc = self.rdv_loc(e_x, u.location, 0) # t should be random
if rdv_loc is None:
self.report_to_server(e_x, "rdv location generate failed")
continue
if u.is_out_of_service_zone(rdv_loc):
self.report_to_server(e_x, "Out of Service")
continue
path_len += get_distance(u.location, rdv_loc)
u.location = rdv_loc
e_x.location = rdv_loc
# list_1 = filter(lambda x: x[3] <= 0.3 and x[2] < 5, list_1)
s_uncharged_electric_vehicles.remove(e_x)
n_charge_req = n_charge_req + 1
return Result(path_len, n_charge_req)
if __name__ == '__main__':
edf_runner = EDFGenerator()
un_charged_vehicles = [
Vehicle(1, Location(1, 1), 2),
Vehicle(2, Location(5, 10), 16),
Vehicle(3, Location(6, 10), 13),
Vehicle(4, Location(8, 11), 22),
Vehicle(5, Location(5, 6), 35),
]
edf_result = edf_runner.get_edf(un_charged_vehicles)
print("first_path_len - edf : ")
edf_result.print()
Where am I wrong? What needs fix?

Move Robot in a 10 X 10 grid

I am working on this code challenge:
Given a 2D bot/robot which can only move in four directions, move forward which is UP(U), move backward which is DOWN(D), LEFT(L), RIGHT(R) in a 10x10 grid. The robot can't go beyond the 10x10 area.
Given a string consisting of instructions to move.
Output the coordinates of a robot after executing the instructions. Initial position of robot is at origin(0, 0).
Example:
Input : move = “UDDLRL”
Output : (-1, -1)
Explanation:
Move U : (0, 0)–(0, 1)
Move D : (0, 1)–(0, 0)
Move D : (0, 0)–(0, -1)
Move L : (0, -1)–(-1, -1)
Move R : (-1, -1)–(0, -1)
Move L : (0, -1)–(-1, -1)
Therefore final position after the complete
movement is: (-1, -1)
I got the code working without using the 10x10 grid information. How could I incorporate the 10x10 grid information into my solution in an OOP fashion? My solution doesn't follow the OOP principles.
# function to find final position of
# robot after the complete movement
def finalPosition(move):
l = len(move)
countUp, countDown = 0, 0
countLeft, countRight = 0, 0
# traverse the instruction string 'move'
for i in range(l):
# for each movement increment its respective counter
if (move[i] == 'U'):
countUp += 1
elif(move[i] == 'D'):
countDown += 1
elif(move[i] == 'L'):
countLeft += 1
elif(move[i] == 'R'):
countRight += 1
# required final position of robot
print("Final Position: (", (countRight - countLeft),
", ", (countUp - countDown), ")")
# Driver code
if __name__ == '__main__':
move = "UDDLLRUUUDUURUDDUULLDRRRR"
finalPosition(move)
This fixes it:
class Robot:
class Mover:
def __init__(self, x, y):
self.x, self.y = x, y
def new_pos(self, x, y):
new_x = x + self.x
new_y = y + self.y
if (new_x > 9 or new_y > 9):
raise ValueError("Box dimensions are greater than 10 X 10")
return new_x, new_y
WALKS = dict(U=Mover(0, 1), D=Mover(0, -1),
L=Mover(-1, 0), R=Mover(1, 0))
def move(self, moves):
x = y = 0
for id in moves:
x, y = self.WALKS[id].new_pos(x, y)
return (x,y)
if __name__ == '__main__':
moves2 = "UDDLLRUUUDUURUDDUULLDRRRR"
robot = Robot()
print(robot.move(moves2))
Output :
(2,3)
The way you use your counters makes it less trivial to detect that you would hit the border of the 10x10 grid. Without changing too much, you could replace the countUp and countDown variables by one countVertical variable, and add -1 to it when going up and 1 when going down. Then ignore a move if it would make that counter negative or greater than 9. And obviously you would do the same for horizontal movements.
[Edit: After the edit to your question, it turns out that you want the Y-coordinate to be opposite to what I assumed above. So I have changed the sign of the Y-coordinate updates (+1, -1).]
That's really it.
Now to make this more OOP, you could define a Robot class, which would maintain its x and y coordinate. Anyhow it would be good to remove the print call out of your function, so the function only deals with the movements, not with the reporting (separation of concern).
Here is how it could work:
class Robot:
def __init__(self, x=0, y=0):
self.position(x, y)
def position(self, x, y):
self.x = min(9, max(0, x))
self.y = min(9, max(0, y))
def move(self, moves):
for move in moves:
if move == 'U':
self.position(self.x, self.y + 1)
elif move == 'D':
self.position(self.x, self.y - 1)
elif move == 'L':
self.position(self.x - 1, self.y)
elif move == 'R':
self.position(self.x + 1, self.y)
else:
raise ValueError(f"Invalid direction '{move}'")
if __name__ == '__main__':
moves = "UDDLLRUUUDUURUDDUULLDRRRR"
robot = Robot(0, 0)
robot.move(moves)
print(f"Final position: {robot.x}, {robot.y}")

Pytorch custom randomcrop for semantic segmentation

I am trying to implement a custom dataset loader. Firstly I resize the images and labels with the same ratio between (0.98, 1,1) then I randomly crop both images and labels with same parameters so that I can feed them into NN. However, I am getting an error from PyTorch functional. Here is my code:
class RandomCrop(object):
def __init__(self, size, padding=None, pad_if_needed=True, fill=0, padding_mode='constant'):
self.size = size
self.padding = padding
self.pad_if_needed = pad_if_needed
self.fill = fill
self.padding_mode = padding_mode
#staticmethod
def get_params(img, output_size):
w, h = img.size
th, tw = output_size
if w == tw and h == th:
return 0, 0, h, w
i = random.randint(0, h - th)
j = random.randint(0, w - tw)
return i, j, th, tw
def __call__(self, data):
img,mask = data["image"],data["mask"]
# pad the width if needed
if self.pad_if_needed and img.size[0] < self.size[1]:
img = F.pad(img, (self.size[1] - img.size[0], 0), self.fill, self.padding_mode)
mask = F.pad(mask, (self.size[1] - mask.size[0], 0), self.fill, self.padding_mode)
# pad the height if needed
if self.pad_if_needed and img.size[1] < self.size[0]:
img = F.pad(img, (0, self.size[0] - img.size[1]), self.fill, self.padding_mode)
mask = F.pad(mask, (0, self.size[0] - mask.size[1]), self.fill, self.padding_mode)
i, j, h, w = self.get_params(img, self.size)
crop_image = transforms.functional.crop(img, i, j, h, w)
crop_mask = transforms.functional.crop(mask, i, j, h, w)
return{"image": crop_image, "mask": crop_mask }
Here is the error:
AttributeError: 'Image' object has no attribute 'dim'
Mistakenly I imported nn.functional.pad instead of the transforms.functional.pad. After changing it everything went smoothly

Which convolution algorithm Keras uses?

I coded a general convolution function in Python for CNNs.
As it turned out the time taken for this function was almost 5x more than the Keras Conv2D takes.
So I was curious if anyone knows why is there a speed difference ?
(It took almost 10-15min for 1 epoch of MNIST Dataset for my convolution function. Whereas Keras does it in almost 3-4min)
Heres my Conv class :
class Convolutional2D(Layer):
def __init__(self, kernel_size, feature_maps):
self.kernel_size = kernel_size
self.feature_maps = feature_maps
self.b = np.zeros((feature_maps))#np.random.rand(feature_maps)
def connect(self, to_layer):
if len(to_layer.layer_shape) == 2:
kernel_shape = [self.feature_maps, self.kernel_size, self.kernel_size]
self.layer_shape = [self.feature_maps] + list(np.array(to_layer.layer_shape)-self.kernel_size+1)
else:
kernel_shape = [self.feature_maps, to_layer.layer_shape[0], self.kernel_size, self.kernel_size]
self.layer_shape = [self.feature_maps] + list(np.array(to_layer.layer_shape[1:])-self.kernel_size+1)
self.kernel = np.random.random(kernel_shape)
super().init_adam_params(self.kernel, self.b)
def convolve(self, x, k, mode='forward'):
if mode == 'forward':
ksize = k.shape[-1]
if len(x.shape) == 3:
out = np.zeros((x.shape[0], k.shape[0], x.shape[1]-k.shape[1]+1, x.shape[2]-k.shape[2]+1))
else:
out = np.zeros((x.shape[0], k.shape[0], x.shape[2]-k.shape[2]+1, x.shape[3]-k.shape[3]+1))
for i in range(out.shape[2]):
for j in range(out.shape[3]):
if len(x.shape) == 3:
window = x[:,i:i+ksize,j:j+ksize]
m = np.reshape(window, (window.shape[0], 1, window.shape[1], window.shape[2]))*k
m = np.sum(m, axis=(2,3))
else:
window = x[:,:,i:i+ksize,j:j+ksize]
m = np.reshape(window, (window.shape[0], 1, window.shape[1], window.shape[2], window.shape[3]))*k
m = np.sum(m, axis=(2,3,4))
out[:,:,i,j] = m
return out
elif mode == 'backward_i':
if len(k.shape) == 3:
out = np.zeros((x.shape[0], x.shape[2]+k.shape[1]-1, x.shape[3]+k.shape[2]-1))
x = np.pad(x, ((0, 0), (0, 0), (k.shape[1]-1, k.shape[1]-1), (k.shape[2]-1, k.shape[2]-1)))
else:
out = np.zeros((x.shape[0], k.shape[1], x.shape[2]+k.shape[2]-1, x.shape[3]+k.shape[3]-1))
x = np.pad(x, ((0, 0), (0, 0), (k.shape[2]-1, k.shape[2]-1), (k.shape[3]-1, k.shape[3]-1)))
fk = np.transpose(k, axes=(1,0,2,3))
x = np.reshape(x, (x.shape[0], 1, x.shape[1], x.shape[2], x.shape[3]))
ksize = k.shape[-1]
for i in range(out.shape[-2]):
for j in range(out.shape[-1]):
if len(k.shape) == 3:
window = x[:,:,i:i+ksize,j:j+ksize]
m = window*k
m = np.sum(m, axis=(1,2,3))
out[:,i,j] = m
else:
window = x[:,:,:,i:i+ksize,j:j+ksize]
m = window*fk
m = np.sum(m, axis=(2,3,4))
out[:,:,i,j] = m
return out
elif mode == 'backward_k':
if len(x.shape) == 3:
out = np.zeros((k.shape[1], x.shape[1]-k.shape[2]+1, x.shape[2]-k.shape[3]+1))
else:
out = np.zeros((k.shape[1], x.shape[1], x.shape[2]-k.shape[2]+1, x.shape[3]-k.shape[3]+1))
x = np.transpose(x, axes=(1,0,2,3))
x = np.reshape(x, (x.shape[0], x.shape[1], x.shape[2], x.shape[3]))
ksize = k.shape[-1]
k = np.transpose(k, axes=(1,0,2,3))
if len(x.shape) != 3:
fk = np.reshape(k, (k.shape[0], 1, k.shape[1], k.shape[2], k.shape[3]))
for i in range(out.shape[-2]):
for j in range(out.shape[-1]):
if len(x.shape) == 3:
window = x[:,i:i+ksize,j:j+ksize]
m = window*k
m = np.sum(m, axis=(1,2,3))
out[:,i,j] = m
else:
window = x[:,:,i:i+ksize,j:j+ksize]
m = window*fk
m = np.sum(m, axis=(2,3,4))
out[:,:,i,j] = m
return out
def forward(self, x):
return self.convolve(x, self.kernel)
def backward(self, x, loss_grad, params):
if len(self.kernel.shape) == 3:
flipped_kernel = np.flip(self.kernel, axis=(1,2))
flipped_loss_grad = np.flip(loss_grad, axis=(1,2))
else:
flipped_kernel = np.flip(self.kernel, axis=(2,3))
flipped_loss_grad = np.flip(loss_grad, axis=(2,3))
i_grad = self.convolve(loss_grad, flipped_kernel, mode='backward_i')
k_grad = self.convolve(x, flipped_loss_grad, mode='backward_k')
self.vw = params['beta1']*self.vw + (1-params['beta1'])*k_grad
self.sw = params['beta2']*self.sw + (1-params['beta2'])*(k_grad**2)
self.kernel += params['lr']*self.vw/np.sqrt(self.sw+params['eps'])
return i_grad
def get_save_data(self):
return {'type':'Convolutional2D', 'shape':np.array(self.layer_shape).tolist(), 'data':[self.kernel_size, self.feature_maps, self.kernel.tolist()]}
def load_saved_data(data):
obj = Convolutional2D(data['data'][0], data['data'][1])
obj.layer_shape = data['shape']
obj.kernel = np.array(data['data'][2])
obj.init_adam_params(obj.kernel, obj.b)
return obj
Keras and Pytorch are much more efficient because they take advantage of vectorization and the fact that matrix multiplication is very well optimized. They basically convert the convolution into a matrix multiplication by flattening the filter and creating a new matrix whose column values are the values of each block. They also take advantage of how the data is stored in memory. You can find more information in this article: https://towardsdatascience.com/how-are-convolutions-actually-performed-under-the-hood-226523ce7fbf

Resources