HyperAI
Back to Headlines

إنشاء نموذج تعلم آلي لاكتشاف العناصر المشعة باستخدام الطيفガンما في Python

منذ 3 أيام

تحليل البيانات الاستكشافي: طيف جاما في بايثون (الجزء الثاني) في الجزء الأول من هذه السلسلة، قمنا بتحليل استكشافي للبيانات التي تم جمعها من طيف جاما. تمكنا من تحديد أن الكشف عن الإشعاع باستخدام أجهزة التوهج الحديثة ليس فقط يكشف عن وجود الإشعاع، بل يمكنه أيضًا تحديد نوع النظائر المشعة الموجودة في الجسم. في هذا الجزء، سنذهب أبعد من ذلك وسأظهر كيفية إنشاء وتدريب نموذج التعلم الآلي للكشف عن العناصر المشعة. تحذير هام جميع ملفات البيانات المستخدمة في هذه المقالة متاحة على Kaggle، مما يتيح للقراء تدريب واختبار نماذجهم الخاصة دون الحاجة إلى الأجهزة الحقيقية. إذا كنت ترغب في اختبار الأجسام الحقيقية، يجب عليك القيام بذلك على مسؤوليتك الخاصة. لقد أجريت الاختبارات باستخدام مصادر يمكن الحصول عليها قانونيًا مثل الزجاج اليورانيوم القديم والساعات القديمة المطلية بالراديوم. تحقق من القوانين المحلية واقرأ الإرشادات الأمنية المتعلقة بمعالجة المواد المشعة. المنهجية 1. طيف جاما طيف جاما هو رسومي يظهر عدد الجسيمات المشعة التي تم اكتشافها حسب طاقتها. هذا الفرق بين جهاز جايجر العادي وجهاز التوهج هو أن الأخير يمكنه الكشف عن الطاقة، مما يجعل كل مادة مشعة لها "بصمة" فريدة. كمثال، اشتريت هذا القطعة من الحلي من متجر صيني: ![صورة من صاحب المقالة] كانت تُعلن أنها تُولد أيونات، مما جعلني أشك في أنها قد تكون مشعة قليلاً. كما نرى على شاشة الجهاز، مستوى الإشعاع حوالي 1,20 ميكروسيفر/ساعة، وهو أعلى بـ 12 مرة من الخلفية الطبيعية (0,1 ميكروسيفر/ساعة). بالنظر إلى الطيف، يمكننا رؤية النظائر الموجودة في القطعة: ![صورة من صاحب المقالة] في هذا المثال، تحتوي القطعة على الثوريوم-232، وسلسلة تحلل الثوريوم تنتج الراديوم والأكتينيوم. يتمظهر الأكتينيوم-228 بشكل واضح على الطيف. كمثال آخر، لنفترض أننا وجدنا هذا الصخرة: ![صورة من ويكيبيديا] هذه الصخرة هي يورانيت، وهي معدن يحتوي على الكثير من أكسيد اليورانيوم. يمكن العثور على مثل هذه العينات في بعض المناطق في ألمانيا أو التشيك أو الولايات المتحدة. عند استخدام طيف جاما، يمكننا رؤية صورة مشابهة: ![صورة من صاحب المقالة] بالمقارنة بين القمم مع النظائر المعروفة، يمكننا تحديد أن الصخرة تحتوي على اليورانيوم وليس الثوريوم. جمع البيانات 2.1 تحديات جمع البيانات التحدي الأول هو جمع العينات. ككاتبة مستقلة، ليس لدي الوصول إلى مصادر معايرة مثل السيزيوم أو السترونتيوم. ومع ذلك، هناك مواد يمكن شراؤها قانونيًا، مثل الأميرسيوم المستخدم في أجهزة الكشف عن الدخان، والراديوم المستخدم في طلاء عقارب الساعات قبل عام 1960، والزجاج اليورانيوم القديم، وقضبان التنجستن الثوريومية التي لا تزال تُنتج اليوم. التحدي الثاني هو جمع البيانات. في العالم الحقيقي، يمكن أن يكون هذا الأمر أكثر صعوبة، خاصة عند بناء قاعدة بيانات للمواد المشعة. يستغرق جمع كل طيف من 10 إلى 20 دقيقة، ومن الأفضل الحصول على 10 تسجيلات لكل جسم. يمكن أن تستغرق العملية ساعات، ولا يمكن الحصول على ملايين السجلات بشكل واقعي. 2.2 جمع البيانات باستخدام بايثون سأستخدم جهاز Radiacode 103G للتعرف على الطيف وكتبة مكتبة radiacode المفتوحة المصدر. يمكن تصدير الطيف بتنسيق XML باستخدام تطبيق Radiacode الرسمي على Android، لكن العملية يدوية بطيئة. بدلاً من ذلك، أنشأت نصًا برمجيًا ببايثون يجمع الطيف باستخدام فترات زمنية عشوائية: ```python from radiacode import RadiaCode, RawData, Spectrum import random import time import datetime import json import logging def read_forever(rc: RadiaCode): """قراءة البيانات من الجهاز""" while True: interval_sec = random.randint(1060, 3060) read_spectrum(rc, interval_sec) def read_spectrum(rc: RadiaCode, interval: int): """قراءة وحفظ الطيف""" rc.spectrum_reset() dt = datetime.datetime.now() filename = dt.strftime("spectrum-%Y%m%d%H%M%S.json") logging.debug(f"صُنع طيف لمدة {interval // 60} دقيقة") t_start = time.monotonic() while time.monotonic() - t_start < interval: show_device_data(rc) time.sleep(0.4) spectrum: Spectrum = rc.spectrum() spectrum_save(spectrum, filename) def show_device_data(rc: RadiaCode): """جلب قيم CPS (عدد الجسيمات في الثانية)""" data = rc.data_buf() for record in data: if isinstance(record, RawData): log_str = f"CPS: {int(record.count_rate)}" logging.debug(log_str) def spectrum_save(spectrum: Spectrum, filename: str): """حفظ بيانات الطيف في سجل""" duration_sec = spectrum.duration.total_seconds() data = { "a0": spectrum.a0, "a1": spectrum.a1, "a2": spectrum.a2, "counts": spectrum.counts, "duration": duration_sec, } with open(filename, "w") as f_out: json.dump(data, f_out, indent=4) logging.debug(f"تم حفظ الملف '{filename}'") rc = RadiaCode() read_forever(rc) ``` تدريب النموذج 3.1 تحميل البيانات عند جمع البيانات، يجب أن يكون لدينا ملفات طيف محفوظة بتنسيق JSON. الملف الفردي يبدو كالتالي: json { "a0": 24.524023056030273, "a1": 2.2699732780456543, "a2": 0.0004327862989157, "counts": [ 48, 52, ..., 0, 35], "duration": 1364.0 } سنقوم بإنشاء طريقة لتحميل الطيف من الملف: ```python from dataclasses import dataclass import numpy as np @dataclass class Spectrum: """بيانات قياس طيف الإشعاع""" duration: int a0: float a1: float a2: float counts: list[int] def channel_to_energy(self, ch: int) -> float: """تحويل رقم القناة إلى مستوى الطاقة""" return self.a0 + self.a1 * ch + self.a2 * ch**2 def energy_to_channel(self, e: float): """تحويل الطاقة إلى رقم القناة (عكس E = a0 + a1*C + a2*C^2)""" c = self.a0 - e return int( (np.sqrt(self.a1**2 - 4 * self.a2 * c) - self.a1) / (2 * self.a2) ) def load_spectrum_json(filename: str) -> Spectrum: """تحميل الطيف من ملف JSON""" with open(filename) as f_in: data = json.load(f_in) return Spectrum( a0=data["a0"], a1=data["a1"], a2=data["a2"], counts=data["counts"], duration=int(data["duration"]), ) ``` 3.2 معالجة البيانات 3.2.1 التطبيع تظهر بيانات الطيف عدد الجسيمات المشعة التي تم اكتشافها حسب طاقتها. ومع ذلك، فإن عدد الجسيمات ليس ثابتًا؛ فهو يعتمد على وقت الجمع وقوة المصادر. لحل هذه المشكلة، طبقت مرشحًا لتخفيض الضوضاء وتطبيع البيانات: ```python from scipy.signal import savgol_filter def smooth_data(data: np.array) -> np.array: """تطبيق مرشح تسوية للبيانات""" window_size = 10 data_out = savgol_filter( data, window_length=window_size, polyorder=2, ) return np.clip(data_out, a_min=0, a_max=None) def normalize(spectrum: Spectrum) -> Spectrum: """تطبيع البيانات إلى نطاق 0..1""" counts = np.array(spectrum.counts).astype(np.float64) counts = smooth_data(counts) val_norm = counts.max() return Spectrum( duration=spectrum.duration, a0=spectrum.a0, a1=spectrum.a1, a2=spectrum.a2, counts=counts / val_norm ) ``` 3.2.2 زيادة البيانات لزيادة حجم قاعدة البيانات، قمت بإضافة ضوضاء عشوائية إلى الطيف الأصلي: python def add_noise(spectrum: Spectrum) -> Spectrum: """إضافة ضوضاء عشوائية إلى الطيف""" counts = np.array(spectrum.counts) ch_empty = spectrum.energy_to_channel(680.0) val_norm = counts[ch_empty] ampl = val_norm / 2 noise = np.random.normal(0, ampl, counts.shape) data_out = np.clip(counts + noise, min=0) return Spectrum( duration=spectrum.duration, a0=spectrum.a0, a1=spectrum.a1, a2=spectrum.a2, counts=data_out ) 3.2.3 استخراج الميزات بدلاً من استخدام جميع 1024 قيمة في الطيف، قمت باستخراج الميزات المرتبطة بالنظائر المهمة فقط: ```python isotopes = [ # الأميرسيوم ("Am-241", 59.5), # البوتاسيوم ("K-40", 1460.0), # الراديوم ("Ra-226", 186.2), ("Pb-214", 242.0), ("Pb-214", 295.2), ("Pb-214", 351.9), ("Bi-214", 609.3), ("Bi-214", 1120.3), ("Bi-214", 1764.5), # الثوريوم ("Pb-212", 238.6), ("Ac-228", 338.2), ("TI-208", 583.2), ("AC-228", 911.2), ("AC-228", 969.0), # اليورانيوم ("Th-234", 63.3), ("Th-231", 84.2), ("Th-234", 92.4), ("Th-234", 92.8), ("U-235", 143.8), ("U-235", 185.7), ("U-235", 205.3), ("Pa-234m", 766.4), ("Pa-234m", 1000.9), ] def isotopes_save(filename: str): """حفظ قائمة النظائر في ملف""" with open(filename, "w") as f_out: json.dump(isotopes, f_out) def get_features(spectrum: Spectrum, isotopes: List) -> np.array: """استخراج الميزات من الطيف""" energies = [energy for _, energy in isotopes] data = [spectrum.counts[spectrum.energy_to_channel(energy)] for energy in energies] return np.array(data) ``` تدريب النموذج 3.3 تدريب النموذج بعد معالجة البيانات، أصبحنا جاهزين لتدريب النموذج. سأستخدم نموذج XGBoost المفتوح المصدر: ```python from xgboost import XGBClassifier from sklearn.preprocessing import LabelEncoder from sklearn.model_selection import GridSearchCV import glob import numpy as np all_files = [ ("Americium", glob.glob("../data/train/americium.json")), ("Radium", glob.glob("../data/train/radium.json")), ("Thorium", glob.glob("../data/train/thorium.json")), ("Uranium Glass", glob.glob("../data/train/uraniumGlass.json")), ("Uranium Glaze", glob.glob("../data/train/uraniumGlaze.json")), ("Uraninite", glob.glob("../data/train/uraninite.json")), ("Background", glob.glob("../data/train/background*.json")), ] def prepare_data(augmentation: int) -> Tuple[np.array, np.array]: """تحضير البيانات للتدريب""" x, y = [], [] for name, files in all_files: for filename in files: print(f"معالجة {filename}...") sp = normalize(load_spectrum_json(filename)) for _ in range(augmentation): sp_out = add_noise(sp) x.append(get_features(sp_out, isotopes)) y.append(name) return np.array(x), np.array(y) X_train, y_train = prepare_data(augmentation=10) le = LabelEncoder() le.fit(y_train) y_train = le.transform(y_train) bst = XGBClassifier() clf = GridSearchCV( bst, { "max_depth": [1, 2, 3, 4], "n_estimators": range(2, 20), "learning_rate": [0.001, 0.01, 0.1, 1.0, 10.0] }, verbose=1, n_jobs=1, cv=3, ) clf.fit(X_train, y_train) print("best_score:", clf.best_score_) print("best_params:", clf.best_params_) ``` اختبار النموذج أخيرًا، سنتesting النموذج في الوقت الحقيقي باستخدام جهاز Radiacode: ```python import xmltodict def load_spectrum_xml(file_path: str) -> Spectrum: """تحميل الطيف من ملف Radiacode Android""" with open(file_path) as f_in: doc = xmltodict.parse(f_in.read()) result = doc["ResultDataFile"]["ResultDataList"]["ResultData"] spectrum = result["EnergySpectrum"] cal = spectrum["EnergyCalibration"]["Coefficients"]["Coefficient"] a0, a1, a2 = float(cal[0]), float(cal[1]), float(cal[2]) duration = int(spectrum["MeasurementTime"]) data = spectrum["Spectrum"]["DataPoint"] return Spectrum( duration=duration, a0=a0, a1=a1, a2=a2, counts=[int(x) for x in data], ) le = LabelEncoder() le.classes_ = np.load("../models/V1/LabelEncoder.npy") isotopes = isotopes_load("../models/V1/isotopes.json") bst = XGBClassifier() bst.load_model("../models/V1/XGBClassifier.json") test_data = [ ["../data/test/background1.xml", "../data/test/background2.xml"], ["../data/test/thorium1.xml", "../data/test/thorium2.xml"], ["../data/test/uraniumGlass1.xml", "../data/test/uraniumGlass2.xml"], ... ] for group in test_data: data = [] for filename in group: spectrum = load_spectrum_xml(filename) features = get_features(normalize(spectrum), isotopes) data.append(features) X_test = np.array(data) preds = bst.predict(X_test) preds = le.inverse_transform(preds) print(preds) ``` الاستخدام في الوقت الحقيقي الكود النهائي للمستخدم في الوقت الحقيقي يشبه الكود السابق، لكنه يجمع الطيف مرة واحدة كل دقيقة ويستخدمه للتنبؤ بالنظائر: ```python from radiacode import RadiaCode, RealTimeData, Spectrum import time import logging logging.basicConfig(level=logging.DEBUG, format="[%(asctime)-15s] %(message)s", datefmt="%Y-%m-%d %H:%M:%S") rc = RadiaCode() logging.debug(f"تم تحميل النموذج") fw_version = rc.fw_version() logging.debug(f"تم الاتصال بالجهاز، البرنامج الثابت {fw_version[1]}") rc.spectrum_reset() while True: for _ in range(12): read_cps(rc) time.sleep(5.0) read_spectrum(rc) ``` الاستنتاج في هذه المقالة، شرحت عملية إنشاء نموذج التعلم الآلي لتحديد النظائر المشعة. قمت باختبار النموذج باستخدام بعض العينات المشعة التي يمكن الحصول عليها قانونيًا. هناك عدة طرق لتحسين النموذج: - إضافة المزيد من عينات البيانات والنظائر. - إضافة المزيد من الميزات، مثل مستوى الإشعاع للجسيمات. - اختبار أنواع أخرى من النماذج، مثل البحث عن المتجهات أو النماذج العميقة. استخدمت جهاز Radiacode في هذه المقالة. إنه جهاز جيد يتيح إجراء تجارب مثيرة للاهتمام. جميع البيانات المستخدمة متاحة مجانًا على Kaggle. الرمز البرمجي الكامل للمقالة متاح على صفحتي على Patreon، ودعم القراء يساعدني في شراء المعدات والالكترونيات للتجارب المستقبلية. يمكن للقراء أيضًا التواصل معي عبر LinkedIn حيث أنشر مشاركات أصغر بانتظام. شكراً لقرائكم!

Related Links