ठीक है! मैं अंत में लगातार कुछ काम पाने में कामयाब रहा हूँ! इस समस्या ने मुझे कई दिनों तक खींचा ... मजेदार चीजें! इस उत्तर की लंबाई के लिए क्षमा करें, लेकिन मुझे कुछ चीजों पर थोड़ा विस्तार करने की आवश्यकता है ... (हालांकि मैं अब तक के सबसे लंबे गैर-स्पैम स्टैकओवरफ़्लो उत्तर के लिए एक रिकॉर्ड स्थापित कर सकता हूं!)
एक साइड नोट के रूप में, मैं पूर्ण डेटासेट का उपयोग कर रहा हूं जिसे Ivo ने अपने मूल प्रश्न में लिंक प्रदान किया है । यह rar फ़ाइलों (एक-प्रति-कुत्ता) की एक श्रृंखला है, जिसमें कई अलग-अलग प्रयोग होते हैं, जिन्हें अस्सी सरणियों के रूप में संग्रहीत किया जाता है। इस प्रश्न में स्टैंड-अलोन कोड उदाहरणों को कॉपी-पेस्ट करने की कोशिश करने के बजाय, यहां पूर्ण, स्टैंड-अलोन कोड के साथ एक बिटकॉइन मर्क्यूरियल रिपॉजिटरी है । आप इसके साथ क्लोन कर सकते हैं
hg clone https://joferkington@bitbucket.org/joferkington/paw-analysis
अवलोकन
समस्या को हल करने के लिए अनिवार्य रूप से दो तरीके हैं, जैसा कि आपने अपने प्रश्न में नोट किया है। मैं वास्तव में दोनों को अलग-अलग तरीकों से उपयोग करने जा रहा हूं।
- पंजा प्रभाव जो निर्धारित करने के लिए पंजा प्रभाव (अस्थायी और स्थानिक) का उपयोग करें।
- "पावप्रिंट" को शुद्ध रूप से उसके आकार के आधार पर पहचानने का प्रयास करें।
मूल रूप से, पहली विधि कुत्ते के पंजे के साथ काम करती है जो ऊपर दिए गए Ivo के प्रश्न में दिखाए गए ट्रैपेज़ॉइडल-जैसे पैटर्न का पालन करती है, लेकिन जब भी पंजे उस पैटर्न का पालन नहीं करते हैं, तब तक विफल रहता है। जब यह काम नहीं करता है तो प्रोग्रामेटिकली पता लगाना काफी आसान है।
इसलिए, हम माप का उपयोग कर सकते हैं जहां इसने एक प्रशिक्षण डाटासेट (~ 2000 पंजा प्रभाव से ~ 30 अलग-अलग कुत्तों) को बनाने के लिए काम किया था ताकि पहचाना जा सके कि कौन सा पंजा है, और समस्या एक पर्यवेक्षित वर्गीकरण (कुछ अतिरिक्त झुर्रियों के साथ) को कम करती है। .. छवि पहचान एक "सामान्य" पर्यवेक्षित वर्गीकरण समस्या की तुलना में थोड़ा कठिन है)।
पैटर्न विश्लेषण
पहली विधि के बारे में विस्तार से बताने के लिए, जब एक कुत्ता चल रहा है (नहीं चल रहा है!) आम तौर पर (इनमें से कुछ कुत्ते नहीं हो सकते हैं), हम पंजे के प्रभाव में उम्मीद करते हैं: फ्रंट लेफ्ट, हिंद राइट, फ्रंट राइट, हिंद लेफ्ट , फ्रंट लेफ्ट आदि पैटर्न या तो फ्रंट लेफ्ट या फ्रंट राइट पॉव से शुरू हो सकता है।
यदि यह हमेशा होता था, तो हम प्रारंभिक संपर्क समय द्वारा प्रभावों को क्रमबद्ध कर सकते हैं और उन्हें पंजे द्वारा समूहित करने के लिए एक modulo 4 का उपयोग कर सकते हैं।
हालांकि, यहां तक कि जब सब कुछ "सामान्य" है, तो यह काम नहीं करता है। यह पैटर्न के ट्रेपोजॉइड जैसी आकृति के कारण है। एक पंजा पंजा स्थानिक रूप से पिछले सामने के पंजे के पीछे पड़ता है।
इसलिए, प्रारंभिक मोर्चा पंजा प्रभाव के बाद हिंद पंजा प्रभाव अक्सर सेंसर प्लेट से गिर जाता है, और दर्ज नहीं किया जाता है। इसी तरह, आखिरी पंजा प्रभाव अक्सर अनुक्रम में अगला पंजा नहीं होता है, क्योंकि पंजा प्रभाव इससे पहले कि सेंसर प्लेट बंद हो गया और रिकॉर्ड नहीं किया गया।
फिर भी, हम यह निर्धारित करने के लिए पंजा प्रभाव पैटर्न के आकार का उपयोग कर सकते हैं कि यह कब हुआ है, और क्या हमने बाएं या दाएं सामने के पंजे के साथ शुरू किया है। (मैं वास्तव में यहाँ पिछले प्रभाव के साथ समस्याओं की अनदेखी कर रहा हूँ। हालांकि, इसे जोड़ना बहुत कठिन नहीं है, हालांकि।)
def group_paws(data_slices, time):
# Sort slices by initial contact time
data_slices.sort(key=lambda s: s[-1].start)
# Get the centroid for each paw impact...
paw_coords = []
for x,y,z in data_slices:
paw_coords.append([(item.stop + item.start) / 2.0 for item in (x,y)])
paw_coords = np.array(paw_coords)
# Make a vector between each sucessive impact...
dx, dy = np.diff(paw_coords, axis=0).T
#-- Group paws -------------------------------------------
paw_code = {0:'LF', 1:'RH', 2:'RF', 3:'LH'}
paw_number = np.arange(len(paw_coords))
# Did we miss the hind paw impact after the first
# front paw impact? If so, first dx will be positive...
if dx[0] > 0:
paw_number[1:] += 1
# Are we starting with the left or right front paw...
# We assume we're starting with the left, and check dy[0].
# If dy[0] > 0 (i.e. the next paw impacts to the left), then
# it's actually the right front paw, instead of the left.
if dy[0] > 0: # Right front paw impact...
paw_number += 2
# Now we can determine the paw with a simple modulo 4..
paw_codes = paw_number % 4
paw_labels = [paw_code[code] for code in paw_codes]
return paw_labels
इस सब के बावजूद, यह अक्सर सही ढंग से काम नहीं करता है। पूर्ण डाटासेट में कई कुत्ते दौड़ते हुए दिखाई देते हैं, और पंजे का प्रभाव उसी अस्थायी क्रम का पालन नहीं करता है जब कुत्ता चल रहा होता है। (या शायद कुत्ते को सिर्फ कूल्हे की गंभीर समस्या है ...)
सौभाग्य से, हम अभी भी प्रोग्राम का पता लगा सकते हैं कि पंजा प्रभाव हमारे अपेक्षित स्थानिक पैटर्न का पालन करता है या नहीं:
def paw_pattern_problems(paw_labels, dx, dy):
"""Check whether or not the label sequence "paw_labels" conforms to our
expected spatial pattern of paw impacts. "paw_labels" should be a sequence
of the strings: "LH", "RH", "LF", "RF" corresponding to the different paws"""
# Check for problems... (This could be written a _lot_ more cleanly...)
problems = False
last = paw_labels[0]
for paw, dy, dx in zip(paw_labels[1:], dy, dx):
# Going from a left paw to a right, dy should be negative
if last.startswith('L') and paw.startswith('R') and (dy > 0):
problems = True
break
# Going from a right paw to a left, dy should be positive
if last.startswith('R') and paw.startswith('L') and (dy < 0):
problems = True
break
# Going from a front paw to a hind paw, dx should be negative
if last.endswith('F') and paw.endswith('H') and (dx > 0):
problems = True
break
# Going from a hind paw to a front paw, dx should be positive
if last.endswith('H') and paw.endswith('F') and (dx < 0):
problems = True
break
last = paw
return problems
इसलिए, भले ही सरल स्थानिक वर्गीकरण हर समय काम नहीं करता है, हम यह निर्धारित कर सकते हैं कि यह कब उचित आत्मविश्वास के साथ काम करता है।
प्रशिक्षण डेटासेट
पैटर्न-आधारित वर्गीकरणों से, जहां यह सही ढंग से काम करता है, हम सही ढंग से वर्गीकृत पंजे के एक बहुत बड़े प्रशिक्षण डेटासेट का निर्माण कर सकते हैं (~ 3200 कुत्तों से 2400 पंजा प्रभाव!)।
अब हम यह देखना शुरू कर सकते हैं कि "औसत" फ्रंट लेफ्ट, आदि, पंजा कैसा दिखता है।
ऐसा करने के लिए, हमें किसी प्रकार के "पंजा मीट्रिक" की आवश्यकता होती है जो किसी भी कुत्ते के लिए समान आयाम है। (पूर्ण डाटासेट में, दोनों बहुत बड़े और बहुत छोटे कुत्ते हैं!) एक आयरिश एल्खाउंड से एक पंजा प्रिंट एक खिलौना पूडल से एक पंजा प्रिंट की तुलना में बहुत व्यापक और बहुत "भारी" होगा। हमें प्रत्येक पंजा प्रिंट को पुनर्विक्रय करने की आवश्यकता है ताकि a) उनके पास समान संख्या में पिक्सेल हों, और b) दबाव मान मानकीकृत हों। ऐसा करने के लिए, मैंने 20x20 ग्रिड पर प्रत्येक पंजा प्रिंट को फिर से आकार दिया और पंजे के प्रभाव के लिए अधिकतम, न्यूनतम, और दबाव के मूल्य के आधार पर दबाव मानों को फिर से बढ़ाया।
def paw_image(paw):
from scipy.ndimage import map_coordinates
ny, nx = paw.shape
# Trim off any "blank" edges around the paw...
mask = paw > 0.01 * paw.max()
y, x = np.mgrid[:ny, :nx]
ymin, ymax = y[mask].min(), y[mask].max()
xmin, xmax = x[mask].min(), x[mask].max()
# Make a 20x20 grid to resample the paw pressure values onto
numx, numy = 20, 20
xi = np.linspace(xmin, xmax, numx)
yi = np.linspace(ymin, ymax, numy)
xi, yi = np.meshgrid(xi, yi)
# Resample the values onto the 20x20 grid
coords = np.vstack([yi.flatten(), xi.flatten()])
zi = map_coordinates(paw, coords)
zi = zi.reshape((numy, numx))
# Rescale the pressure values
zi -= zi.min()
zi /= zi.max()
zi -= zi.mean() #<- Helps distinguish front from hind paws...
return zi
इस सब के बाद, हम अंत में एक औसत बाएं बाएं, हिंद दाएं, आदि पंजा देख सकते हैं। ध्यान दें कि यह बहुत अलग-अलग आकार के 30 कुत्तों में औसत है, और हमें लगातार परिणाम मिल रहे हैं!
हालांकि, इससे पहले कि हम इन पर कोई विश्लेषण करते हैं, हमें माध्य (सभी कुत्तों के सभी पैरों के लिए औसत पंजा) को घटाना होगा।
अब हम माध्य से अंतरों का विश्लेषण कर सकते हैं, जिन्हें पहचानना थोड़ा आसान है:
छवि-आधारित पवन मान्यता
ठीक है ... हमारे पास अंत में पैटर्न का एक सेट है जिसे हम पंजे के खिलाफ मैच करने की कोशिश करना शुरू कर सकते हैं। प्रत्येक पंजा को 400-आयामी वेक्टर के रूप में माना जा सकता है (द्वारा लौटाया गया)paw_image
फ़ंक्शन ) के जा सकता है, जिसकी तुलना इन चार-आयामी वेक्टर से की जा सकती है।
दुर्भाग्य से, अगर हम सिर्फ एक "सामान्य" पर्यवेक्षित वर्गीकरण एल्गोरिथ्म का उपयोग करते हैं (अर्थात 4 पैटर्न में से कौन सा एक साधारण दूरी का उपयोग करके एक विशेष पंजा प्रिंट के सबसे करीब है), यह लगातार काम नहीं करता है। वास्तव में, यह प्रशिक्षण डाटासेट पर यादृच्छिक मौका से बेहतर नहीं करता है।
यह छवि मान्यता में एक आम समस्या है। इनपुट डेटा की उच्च आयामीता के कारण, और कुछ हद तक "फ़ज़ी" चित्रों की प्रकृति (अर्थात आसन्न पिक्सेल में एक उच्च सह-प्रसार होता है), बस एक छवि के अंतर को छवि से देखना बहुत अच्छा माप नहीं देता है उनके आकार की समानता।
Eigenpaws
इसके इर्द-गिर्द रहने के लिए हमें "ईजीनपाव्स" (चेहरे की पहचान में "आइजनफैस" की तरह) का एक सेट बनाने की आवश्यकता है, और इन इवानपावों के संयोजन के रूप में प्रत्येक पंजा प्रिंट का वर्णन करें। यह प्रमुख घटक विश्लेषण के समान है, और मूल रूप से हमारे डेटा की गतिशीलता को कम करने का एक तरीका प्रदान करता है, ताकि दूरी आकार का एक अच्छा उपाय हो।
क्योंकि हमारे पास आयामों (2400 बनाम 400) से अधिक प्रशिक्षण चित्र हैं, गति के लिए "फैंसी" रैखिक बीजगणित करने की कोई आवश्यकता नहीं है। हम प्रशिक्षण डेटा सेट के सहसंयोजक मैट्रिक्स के साथ सीधे काम कर सकते हैं:
def make_eigenpaws(paw_data):
"""Creates a set of eigenpaws based on paw_data.
paw_data is a numdata by numdimensions matrix of all of the observations."""
average_paw = paw_data.mean(axis=0)
paw_data -= average_paw
# Determine the eigenvectors of the covariance matrix of the data
cov = np.cov(paw_data.T)
eigvals, eigvecs = np.linalg.eig(cov)
# Sort the eigenvectors by ascending eigenvalue (largest is last)
eig_idx = np.argsort(eigvals)
sorted_eigvecs = eigvecs[:,eig_idx]
sorted_eigvals = eigvals[:,eig_idx]
# Now choose a cutoff number of eigenvectors to use
# (50 seems to work well, but it's arbirtrary...
num_basis_vecs = 50
basis_vecs = sorted_eigvecs[:,-num_basis_vecs:]
return basis_vecs
ये basis_vecs
"ईगेनपॉव" हैं।
इनका उपयोग करने के लिए, हम बस वैक्टर (यानी मैट्रिक्स गुणन) आधार वैक्टर के साथ प्रत्येक पंजा छवि (400x वेक्टर के बजाय 20x20 छवि के रूप में) का उपयोग करते हैं। यह हमें 50-आयामी वेक्टर (एक तत्व प्रति आधार वेक्टर) देता है जिसका उपयोग हम छवि को वर्गीकृत करने के लिए कर सकते हैं। प्रत्येक "टेम्पलेट" पंजा की 20x20 छवि के लिए एक 20x20 छवि की तुलना करने के बजाय, हम 50-आयामी रूपांतरित छवि की तुलना प्रत्येक 50-आयामी रूपांतरित टेम्पलेट पंजा से करते हैं। यह बहुत कम छोटे बदलावों के प्रति संवेदनशील है कि कैसे प्रत्येक पैर की अंगुली को तैनात किया जाता है, और मूल रूप से समस्या की गतिशीलता को केवल प्रासंगिक आयामों तक कम कर देता है।
Eigenpaw- आधारित पवन वर्गीकरण
अब हम केवल 50-आयामी वैक्टर और प्रत्येक टेम्पलेट के लिए "टेम्पलेट" वैक्टर के बीच की दूरी को वर्गीकृत करने के लिए उपयोग कर सकते हैं जो यह है:
codebook = np.load('codebook.npy') # Template vectors for each paw
average_paw = np.load('average_paw.npy')
basis_stds = np.load('basis_stds.npy') # Needed to "whiten" the dataset...
basis_vecs = np.load('basis_vecs.npy')
paw_code = {0:'LF', 1:'RH', 2:'RF', 3:'LH'}
def classify(paw):
paw = paw.flatten()
paw -= average_paw
scores = paw.dot(basis_vecs) / basis_stds
diff = codebook - scores
diff *= diff
diff = np.sqrt(diff.sum(axis=1))
return paw_code[diff.argmin()]
यहाँ कुछ परिणाम हैं:
शेष समस्याएं
अभी भी कुछ समस्याएं हैं, विशेष रूप से कुत्तों के साथ एक स्पष्ट पावप्रिंट बनाने के लिए बहुत छोटी ... (यह बड़े कुत्तों के साथ सबसे अच्छा काम करता है, क्योंकि सेंसर के संकल्प पर पैर की उंगलियों को अधिक स्पष्ट रूप से अलग किया जाता है।) इसके अलावा, आंशिक पॉवर्स को इसके साथ पहचाना नहीं जाता है। प्रणाली, जबकि वे ट्रेपोज़ाइडल-पैटर्न-आधारित प्रणाली के साथ हो सकते हैं।
हालाँकि, क्योंकि eigenpaw विश्लेषण स्वाभाविक रूप से एक दूरी की मीट्रिक का उपयोग करता है, हम पंजे को दोनों तरीकों से वर्गीकृत कर सकते हैं, और वापस आकर trapezoidal-pattern- आधारित प्रणाली पर गिर सकते हैं जब eigenpaw विश्लेषण की "कोडबुक" से सबसे छोटी दूरी कुछ सीमा से अधिक है। मैंने इसे अभी तक लागू नहीं किया है, हालांकि।
काहे ... वह लंबा था! इस तरह के एक मजेदार सवाल करने के लिए इवो पर मेरी टोपी बंद है!