【四】3D Object Model之测量Features——get_object_model_3d_params()算子

本博客主要分享Halcon各类算子含义及用法。重点介绍了get_object_model_3d_params()函数,该函数可访问3D对象模型的属性和元数据,还说明了其参数。此外,列出了【3D Object Model之测量Features】待更新的算子,最后给出本专栏博客汇总。

😊😊😊欢迎来到本博客😊😊😊

🌟🌟🌟 Halcon算子太多,学习查找都没有系统的学习查找路径,本专栏主要分享Halcon各类算子含义及用法,有时间会更新具体案例。

😊😊😊 具体食用方式:可以点击本专栏【Halcon算子快速查找】–>搜索你要查询的算子名称;或者点击Halcon算子汇总博客,即可食用。

🎁🎁🎁支持:如果觉得博主的文章还不错或者您用得到的话,可以悄悄关注一下博主哈,如果三连收藏支持就更好啦!这就是给予我最大的支持!😙😙😙


学习目标

  • get_object_model_3d_params()

学习内容

算子简介

函数名解释
get_object_model_3d_params()返回三维对象模型的属性。

1、get_object_model_3d_params()

get_object_model_3d_params( : : ObjectModel3D, GenParamName : GenParamValue)

  函数说明:三维对象模型由一组属性和元数据组成,get_object_model_3d_params允许访问给定3d对象模型的属性和元数据。请求的属性或元数据的名称在通用参数GenParamName中传递,相应的值在GenParamValue中返回。
  如果请求的属性或元数据不可用,则会引发异常。get_object_model_3d_params支持同时访问多个三维对象模型和多个属性
  请注意:属性或元数据可以具有不同的长度。一些标准属性具有定义的长度,如后面介绍的属性描述。其他属性的长度取决于实际的三维对象模型,可以通过将参数GenParamName设置为“num_points”、“num_triangles”、“num_polygons”或“num_lines”来查询。因此,要获得标准属性“point_coord_x”的长度,请将GenParamName设置为“num_points”。

  函数参数:
    ObjectModel3D输入 3D对象模型句柄;
    GenParamName输入为三维对象模型查询的常规属性的名称;默认: ‘num_points’;参考: 'blue', 'bounding_box1', 'center', 'diameter_axis_aligned_bounding_box', 'extended_attribute_names', 'extended_attribute_types', 'green', 'has_distance_computation_data', 'has_extended_attribute', 'has_lines', 'has_point_normals', 'has_points', 'has_polygons', 'has_primitive_data', 'has_primitive_rms', 'has_segmentation_data', 'has_shape_based_matching_3d_data', 'has_surface_based_matching_data', 'has_triangles', 'has_xyz_mapping', 'lines', 'mapping_col', 'mapping_row', 'mapping_size', 'neighbor_distance', 'num_extended_attribute', 'num_lines', 'num_neighbors', 'num_neighbors_fast', 'num_points', 'num_polygons', 'num_primitive_parameter_extension', 'num_triangles', 'original_point_indices', 'point_coord_x', 'point_coord_y', 'point_coord_z', 'point_normal_x', 'point_normal_y', 'point_normal_z', 'polygons', 'primitive_parameter', 'primitive_parameter_extension', 'primitive_parameter_pose', 'primitive_pose', 'primitive_rms', 'primitive_type', 'red', 'reference_point', 'score', 'triangles'
    GenParamValue输出通用参数值;

属性名解释
'point_coord_x'一组3D点的x坐标(长度可以通过num_points查询)。该属性通常从运算符xyz_to_object_model_3dread_object_model_3d获得。
'point_coord_y'一组3D点的y坐标(长度可以通过num_points查询)。该属性通常从运算符xyz_to_object_model_3dread_object_model_3d获得。
'point_coord_z'一组3D点的z坐标(长度可以通过num_points查询)。该属性通常从运算符xyz_to_object_model_3dread_object_model_3d获得。
'point_normal_x'一组3D点的法线x分量(长度可以通过num_points查询)。该属性通常从操作符smooth_object_model_3d中获得。
'point_normal_y'一组3D点的法线y分量(长度可以通过num_points查询)。该属性通常从操作符smooth_object_model_3d中获得。
'point_normal_z'一组3D点的法线z分量(长度可以通过num_points查询)。该属性通常从操作符smooth_object_model_3d中获得。
'score'一组3D点的分数(长度可以通过num_points查询)。该属性通常从操作符reconstruct_surface_stereo中获得。
'red'一组3D点的red通道(长度可以通过num_points查询)。该属性通常从操作符reconstruct_surface_stereo中获得。
'green'一组3D点的green通道(长度可以通过num_points查询)。该属性通常从操作符reconstruct_surface_stereo中获得。
'blue'一组3D点的blue通道(长度可以通过num_points查询)。该属性通常从操作符reconstruct_surface_stereo中获得。
'mapping_row'3D点集的二维映射的行组件。(长度可以通过num_points查询)。原始图像的高度可通过mapping_size查询)。该属性通常从运算符xyz_to_object_model_3d中获得。
'mapping_col'3D点集的二维映射的列组件。(长度可以通过num_points查询)。原始图像的高度可通过mapping_size查询)。该属性通常从运算符xyz_to_object_model_3d中获得。
'mapping_size'原始图像的大小。返回一个具有两个条目宽度和高度的元组。
'triangles'按以下顺序表示三角形的三维点的索引前三个值(返回值0,1,2)表示第一个三角形接下来的三个值(返回值3、4、5)表示第二个三角形等所有索引都对应于3D点的坐标。可以访问3D点的坐标,例如,将通用参数GenParamName分别设置为值“point_coord_x”、“point_cord_y”和“point_corde_z”。此属性的长度相当于三角形数量的三倍,可以使用“num_triangles”查询三角形。该属性通常从操作符triangulate_object_model_3dread_object_model_3d获得。
'polygons'按以下顺序表示多边形的三维点的索引第一个返回值包含第一个多边形的点数n。以下值(返回值1,2,…,n)表示第一个多边形的点的索引。下一个值(返回值n+1)包含第二个多边形的点数m。以下m个返回值(返回值n+2,n+3,…,n+1+m)表示第二多边形等的点的索引。所有索引对应于3D点的坐标。可以访问3D点的坐标,例如,将通用参数GenParamName分别设置为值“point_coord_x”、“point_cord_y”和“point_corde_z”。可以使用“num_polygons”查询每个三维对象模型的多边形数。该属性通常从操作符read_object_model_3d中获得。
'lines'按以下顺序表示多段线的三维点的索引第一个返回值包含第一条多段线点数n。以下值(返回值1,2,…,n)表示第一条多段线的点的索引。下一个值(返回值n+1)包含第二条多段线的点数m。以下m个值(返回值n+2,n+3,…,n+1+m)表示第二折线等的点的索引。所有索引对应于3D点的坐标。可以访问3D点的坐标,例如,将通用参数GenParamName分别设置为值“point_coord_x”、“point_cord_y”和“point_corde_z”。可以使用“num_lines”查询每个三维对象模型的行数。该属性通常从操作符intersect_plane_object_model_3d中获得。
'diameter_axis_aligned_bounding_box'一组3D点的直径,定义为最小封闭轴平行长方体的对角线长度(请参见参数’bounding_box1’)。此属性的长度为1。
'center'该组3D点的中心的3D坐标。这些坐标是最小封闭轴平行长方体的中心(请参见参数“bounding_box1”)。此属性的长度为3。如果三维对象模型中没有三维坐标,则以下规则有效
(1) 如果三维对象模型是圆柱体类型的基本体(请参见gen_cylinder_object_model_3D),并且存在扩展,则返回扩展之间的中心点。如果没有扩展,则返回姿势的平移参数;
(2) 如果三维对象模型是类型为平面的基元(请参考gen_plane_object_model_3D),并且存在扩展,则根据扩展计算平面的重心。如果没有扩展,则返回姿势的平移参数;
(3) 如果三维对象模型是球体或长方体类型的基本体(请参考gen_sphere_object_model_3Dgen_box_object_model_3D),则返回对象模型的中心点。
'original_point_indices'不同三维对象模型中三维点的索引(长度可以通过num_points查询)。该属性通常从操作符triangulate_object_model_3d中获得。
'primitive_type'基元类型(例如,从操作符fit_primitives_object_model_3d获得)。球体的返回值为“sphere”。圆柱体的返回值为“cylinder”。平面的返回值为“plane”。box的返回值为“box”。此属性的长度为1。
'primitive_parameter'基元的参数(例如,从操作符fit_primitives_object_model_3d获得)。该属性的长度取决于“primitive_type”,对于每个三维对象模型,长度在4到10之间。
(1) 如果三维对象模型是圆柱体类型的基元(请参见gen_cylinder_object_model_3D),则返回值为中心[x_center,y_center,z_center](x-,y-,z-)坐标、圆柱体主轴[x_axis,y_axis,z_axis](x-,y-,z-)方向以及圆柱体的半径[radius]。顺序为[x_center,y_center,z_center,x_axis,y_axis,z_axis,radius]
(2) 如果三维对象模型是类型为球体的基元(请参见gen_sphere_object_model_3D),则返回值为球体的中心[x_center,y_center,z_center](x-,y-,z-)坐标和半径[radius]。顺序为[x_center,y_center,z_center,radius]
(3) 如果3D对象模型是平面类型的基元(参见gen_plane_object_model_3D),则返回hessian法线形式的4个参数,即单位法线(x-,y-,z-)向量[x,y,z]和平面与坐标系原点的正交距离(d)。顺序是[x,y,z,d]。距离(d)的符号决定了原点所在平面的一侧。
(4) 如果三维对象模型是长方体类型的基元(gen_box_object_model_3D),则返回值为长方体的三维姿势(平移、旋转、旋转类型)和半边长(长度1、长度2、长度3)。length1是长方体沿姿势的x轴的长度。length2是长方体沿着姿势的y轴的长度。length3是长方体沿着姿势的z轴的长度。顺序为[trans_x,trans_y,trans_z,rot_x,rot_y,rot_z,rot_type,length1,length2,length3]。有关三维姿势和相应变换矩阵的详细信息,请参见操作符create_pose
'primitive_parameter_pose'具有3D姿势格式的基元的参数(例如,从操作符fit_primitives_object_model_3D获得)。对于所有类型的基本体,返回值都是3D姿势(平移、旋转、旋转类型)。有关三维姿势和相应变换矩阵的详细信息,请参见操作符create_pose。该属性的长度取决于“primitive_type”,对于每个三维对象模型,该长度在7到10之间。
(1) 如果三维对象模型是圆柱体类型的基元(请参见gen_cylinder_object_model_3D),则还会返回圆柱体的半径[radius]。顺序为[trans_x,trans_y,trans_z,rot_x,rot_y,rot_z,rot_type,radius]
(2) 如果三维对象模型是类型为球体的基元(请参见gen_sphere_object_model_3D),则还会返回球体的半径[radius]。顺序为[trans_x,trans_y,trans_z,rot_x,rot_y,rot_z,rot_type,radius]
(3) 如果三维对象模型是类型为平面的基元(请参见gen_plane_object_model_3D),则顺序为[trans_x,trans_y,trans_z,rot_x,rot_y,rot_z,rot_type]
(4) 如果三维对象模型是长方体类型的基元(请参见gen_box_object_model_3D),则还会返回长方体的半边长(length1、length2、length3)length1是长方体沿姿势的x轴的长度。length2是长方体沿着姿势的y轴的长度。length3是长方体沿着姿势的z轴的长度。顺序为[trans_x,trans_y,trans_z,rot_x,rot_y,rot_z,rot_type,length1,length2,length3]
'primitive_pose'具有3D姿势格式的基元的参数(例如,从操作符fit_primitives_object_model_3D获得)。对于所有类型的基本体,返回值都是3D姿势(平移、旋转、旋转类型)。有关三维姿势和相应变换矩阵的详细信息,请参见操作符create_pose。对于每个三维对象模型,该属性的长度为7。顺序为[trans_x,trans_y,trans_z,rot_x,rot_y,rot_z,rot_type]
'primitive_parameter_extension'圆柱体和平面类型的基元的范围(例如,从操作符fit_primitives_object_model_3d获得)。此属性的长度取决于“primitive_type”,可以使用“num_primitive_parameter_extension”进行查询。
(1) 如果三维对象模型是圆柱体类型的基本体(请参见gen_cylinder_object_model_3D),则返回值为圆柱体的范围(MinExtent、MaxExtent)。它们按[MinExtent,MaxExtent]的顺序返回。MinExtent表示圆柱体在旋转轴负方向上的长度。MaxExtent表示圆柱体在旋转轴正方向上的长度。
(2) 如果三维对象模型是平面类型的基元(使用fit_primitives_object_model_3D创建),则返回值是关于拟合平面的共面点的元组。顺序为[点1的x坐标,点2的x坐标、点3的x坐标…、点1的y坐标、点2的y坐标,点3的y坐标…]。坐标值描述了凸包的支撑点。这是基于拟合平面上有助于拟合的那些点的投影来计算的。如果平面是使用gen_plane_object_model_3d创建的,则会返回用于创建平面的所有点(XExtent、YExtent)
'primitive_rms'基元的二次残差(例如,从算子fit_primitives_object_model_3d获得)。此属性的长度为1。
'reference_point'用于基于形状的3D匹配的所准备的3D形状模型的参考点的3D坐标。参考点是最小封闭轴平行长方体的中心(参见参数“bounding_box1”)。此属性的长度为3。
'bounding_box1'最小封闭轴平行长方体(min_x,min_y,min_z,max_x,max_y,max_z)。此属性的长度为6。
'num_points'点数。此属性的长度为1。
'num_triangles'三角形的数量。此属性的长度为1。
'num_polygons'多边形的数量。此属性的长度为1。
'num_lines'线的数量。此属性的长度为1。
'num_primitive_parameter_extension'基元的扩展数据的数量。此属性的长度为1
'has_points'三维点是否存在。此属性的长度为1。
'has_point_normals'三维点法线是否存在。此属性的长度为1。
'has_triangles'三角形是否存在。此属性的长度为1。
'has_polygons'多边形是否存在。此属性的长度为1。
'has_lines'线是否存在。此属性的长度为1。
'has_xyz_mapping'是否存在3D点到图像坐标的映射。此属性的长度为1。
'has_shape_based_matching_3d_data'是否存在用于基于形状的3D匹配的形状模型。此属性的长度为1。
'has_distance_computation_data'是否存在用于三维距离计算的预计算数据结构。此属性的长度为1。数据结构可以使用prepare_object_model_3d创建,目的是“distance_computerion”。它由操作符distance_object_model_3d使用。
'has_surface_based_matching_data'是否存在用于基于曲面的匹配的数据。此属性的长度为1。
'has_segmentation_data'是否存在用于3D分割的数据。此属性的长度为1。
'has_primitive_data'原始数据是否存在。此属性的长度为1。
'has_primitive_rms'基元的二次残差是否存在。此属性的长度为1。
'neighbor_distance'——
'neighbor_distance N'对于每个点,第N个最近点的距离。 N必须是一个正整数,默认情况下为25。对于每个点,所有其他点都会根据它们的距离进行排序,并返回第N个点的距离。
'num_neighbors X'对于每个点,距离最多为X的邻居的数量
'num_neighbors_fast X'对于每个点,距离最多为X的邻居的近似数量。使用体素来近似距离,与“num_neighbors”相比,处理速度更快。

  扩展属性是指可以通过特殊运算符(例如distance_object_model_3d从标准属性派生的属性,或用户定义的属性。操作员set_object_model_3d_attrib可以创建用户定义的属性。可以访问以下扩展属性和元数据:

属性名解释
'&attribute_name'扩展属性的用户定义名称。请注意,此名称必须以“&”开头,例如“&my_attrib”。请求的扩展属性的数据在GenParamValue中返回。返回数据的顺序与在GenParamName中指定的属性名称的顺序相同。
'extended_attribute_names'所有扩展属性的名称。对于每个扩展属性名称,都会返回一个值。
'extended_attribute_types'所有扩展属性的类型。对于每个扩展属性类型,都会返回一个值,从而将这些值排序为扩展属性名称的输出。
'has_extended_attribute'至少存在一个扩展属性。对于每个三维对象模型,都会返回一个值。
'num_extended_attribute'扩展属性的数量。对于每个三维对象模型,都会返回一个值。

Halcon例程

vector_to_hom_mat3d.hdev							从点对应关系近似不同类型的仿射三维变换
triangulate_object_model_3d_xyz_mapping.hdev		对三维对象模型进行三角化(二维映射)
simplify_object_model_3d.hdev						简化复杂的三维对象模型
select_points_object_model_3d_by_density.hdev		基于点密度删除三维对象模型中有噪声的部分
select_points_object_model_3d.hdev					使用阈值删除三维对象模型的部分
select_object_model_3d.hdev							根据体积和直径选择三维对象模型
segment_object_model_3d.hdev						将2.5D数据分段为圆柱形或球形部分
scene_flow_globe.hdev								计算两个立体图像对之间的3D场景流,以恢复对象在3D中的运动
sample_object_model_3d.hdev							使用采样减少三维对象模型中的点数
reduce_object_model_3d_to_visible_parts.hdev		删除三维对象模型中从定义的摄影机姿势看不到的部分
reconstruct_3d_object_model_for_matching.hdev		通过三维配准从多个视图重建用于匹配的三维对象模型
intersect_plane_object_model_3d.hdev				计算三维对象模型和平面之间的交点
interactive_intersection.hdev						交互式计算三维对象模型和平面之间的交点
inspect_3d_surface_intersections.hdev				通过检查三维对象的平面交点来检查安装凸舌的角度和尺寸
genicamtl_lmi_gocator_objectmodel3d.hdev			使用GenICamTL接口从LMI Technologies Gocator传感器数据生成3D对象模型
fuse_object_model_3d_workflow.hdev					设置多个三维对象模型的三维融合参数
fit_primitives_object_model_3d.hdev					将圆柱体装配到2.5D数据中
find_surface_model_with_edges.hdev					使用边缘支持的基于表面的匹配在3D场景中查找纸箱
find_deformable_surface_model.hdev					使用基于可变形曲面的匹配在三维场景中查找对象
disp_object_model_3d.hdev							显示三维对象模型
check_for_holes_sheet_of_light.hdev					使用校准的光设置表来验证孔的存在和正确性
3d_matching_clamps.hdev								使用三维DXF模型识别图像中的三维对象

【3D Object Model之测量Features】 待更新算子

  • area_object_model_3d()
  • distance_object_model_3d()
  • get_object_model_3d_params()
  • max_diameter_object_model_3d()
  • moments_object_model_3d()
  • select_object_model_3d()
  • smallest_bounding_box_object_model_3d()
  • smallest_sphere_object_model_3d()
  • volume_object_model_3d_relative_to_plane()

汇总

  本专栏博客汇总:Halcon算子汇总


🚶🚶🚶今天的文章就到这里啦~
喜欢的话,点赞👍、收藏⭐️、关注💟哦 ~

<think>我们之前定义的自定义模型类`LlamaWithEncoder`中,我们重写了`prepare_inputs_for_generation`方法,但是在新的`EnhancedLlama`类中,我们没有重写这个方法。此外,我们使用了新的模型结构,将两个Transformer编码器替换为了两个全连接网络。因此,我们需要确保自定义模型支持生成(generation)所需的方法。 在Hugging Face的transformers库中,生成文本时,模型需要具备`prepare_inputs_for_generation`方法。我们可以在新的模型类中重新实现这个方法,或者确保从基础模型中继承。但是,由于我们使用了自定义的输入(formula_vec和spec_matrix),在生成时可能无法提供这些输入,因此我们需要重新考虑生成策略。 然而,在微调阶段,我们通常只需要训练模型,生成(推理)可能是在训练完成后单独进行的。因此,我们可以暂时不实现`prepare_inputs_for_generation`,因为训练过程中不需要它。但是,`Trainer`在训练过程中不会调用这个方法,所以不会出错。但是,当我们保存模型并尝试生成时,就会遇到问题。 为了解决这个问题,我们可以在自定义模型类中添加`prepare_inputs_for_generation`方法,并委托给基础模型。同时,我们需要考虑在生成时如何处理自定义的编码器输入(因为在生成时,我们无法提供完整的formula_vec和spec_matrix,而且生成是逐步进行的)。 由于生成阶段我们可能无法使用质谱和分子式信息(因为生成是自回归的,我们只能逐步生成token),所以我们需要重新设计模型在生成阶段的行为。但是,这超出了当前问题的范围(当前问题是确保训练时LoRA微调正常工作)。 因此,我们暂时忽略生成的问题,专注于训练。但是,为了满足`Trainer`的要求,我们可以添加一个简单的`prepare_inputs_for_generation`方法,直接调用基础模型的对应方法。 修改后的`EnhancedLlama`类: ```python class EnhancedLlama(nn.Module): def __init__(self, base_model, formula_dim=18, spec_dim=258, hidden_dim=512): super().__init__() # ... 初始化代码保持不变 ... def forward(self, ...): # ... forward代码保持不变 ... # 添加prepare_inputs_for_generation方法 def prepare_inputs_for_generation(self, input_ids, past_key_values=None, **kwargs): return self.base_model.prepare_inputs_for_generation( input_ids, past_key_values=past_key_values, **kwargs ) ``` 但是,请注意,在生成阶段,我们无法使用formula_vec和spec_matrix,因此我们需要在生成时禁用这两个输入。这可能需要我们在生成时调整模型的结构(例如,使用一个没有Encoder的模型,或者使用默认的嵌入)。这需要更复杂的设计,但不在当前问题范围内。 现在,回到原始问题:自定义层没有进行LoRA微调。在之前的修改中,我们使用了`modules_to_save`参数,这应该会将这些模块的参数设置为可训练。我们可以通过打印可训练参数来验证。 另外,我们之前定义的`EnhancedLlama`类中,自定义层的参数是否被正确标记为可训练?在`get_peft_model`函数中,`modules_to_save`指定的模块会被添加到可训练参数中。 但是,我们注意到在`EnhancedLlama`类中,我们使用了`nn.Sequential`来定义编码器,这些层在`modules_to_save`中指定了名称(如"formula_encoder"),所以应该会被正确识别。 为了确保自定义Encoder层可以顺利进行LoRA微调,我们还需要注意以下几点: 1. 确保自定义层在模型初始化后被正确添加到模型中(即它们是模型的子模块)。 2. 在`modules_to_save`中指定了这些层的名称(字符串匹配)。 3. 在训练过程中,这些层的参数梯度应该被计算(即`requires_grad=True`)。 我们可以通过以下代码检查: ```python # 在应用LoRA后,打印可训练参数 for name, param in model.named_parameters(): if param.requires_grad: print(name) ``` 如果输出中包含`formula_encoder`和`spec_encoder`以及`fusion`层的参数,则说明配置成功。 现在,我们整合以上内容,给出修改后的代码: ```python # ... [之前的代码,包括常量、函数定义、数据集类等] ... # 带Encoder的自定义模型(添加prepare_inputs_for_generation) class EnhancedLlama(nn.Module): def __init__(self, base_model, formula_dim=18, spec_dim=258, hidden_dim=512): super().__init__() self.base_model = base_model self.model_dim = base_model.config.hidden_size # 分子式编码器 self.formula_encoder = nn.Sequential( nn.Linear(formula_dim, hidden_dim), nn.GELU(), nn.Linear(hidden_dim, self.model_dim) ) # 质谱编码器 self.spec_encoder = nn.Sequential( nn.Linear(spec_dim, hidden_dim), nn.GELU(), nn.Linear(hidden_dim, self.model_dim) ) # 融合层 self.fusion = nn.Linear(2 * self.model_dim, self.model_dim) # 适配基础模型的输入嵌入 self.input_embeddings = base_model.get_input_embeddings() def forward( self, input_ids=None, attention_mask=None, formula_vec=None, spec_matrix=None, labels=None, **kwargs ): # 编码分子式 formula_emb = self.formula_encoder(formula_vec) # 编码质谱数据 spec_emb = self.spec_encoder(spec_matrix) spec_emb = torch.mean(spec_emb, dim=1) # 全局平均池化 # 融合特征 fused = torch.cat([formula_emb.unsqueeze(1), spec_emb.unsqueeze(1)], dim=-1) fused = self.fusion(fused) # 获取输入嵌入 inputs_embeds = self.input_embeddings(input_ids) # 将融合特征与第一个token结合 inputs_embeds[:, 0] = (inputs_embeds[:, 0] + fused.squeeze(1)) / 2.0 # 调用基础模型 return self.base_model( inputs_embeds=inputs_embeds, attention_mask=attention_mask, labels=labels, **kwargs ) # 添加prepare_inputs_for_generation方法 def prepare_inputs_for_generation(self, *args, **kwargs): return self.base_model.prepare_inputs_for_generation(*args, **kwargs) # 加载基础模型 base_model = transformers.AutoModelForCausalLM.from_pretrained( "/root/workspace/checkpoint-2500", trust_remote_code=True, torch_dtype=torch.bfloat16 ) # 创建增强模型 model = EnhancedLlama(base_model) # LoRA配置 - 关键修改:添加modules_to_save lora_config = LoraConfig( r=8, lora_alpha=32, target_modules=["q_proj", "v_proj"], # 目标注意力层 modules_to_save=[ # 明确指定需要训练的自定义层 "formula_encoder", "spec_encoder", "fusion" ], lora_dropout=0.05, bias="none", task_type="CAUSAL_LM" ) # 应用LoRA model = get_peft_model(model, lora_config) model.print_trainable_parameters() # 检查可训练参数(可选,用于调试) print("\nTrainable parameters:") for name, param in model.named_parameters(): if param.requires_grad: print(name) # ... [后续的Trainer定义和训练代码] ... ``` 这样,我们就添加了`prepare_inputs_for_generation`方法以避免训练时出错(虽然训练时不会调用,但为了模型完整性),并且通过`modules_to_save`确保自定义层参与训练。 注意:生成(inference)阶段如何整合自定义输入是一个复杂问题,可能需要将编码器提取出来,在生成前先计算融合特征,然后作为输入嵌入的一部分。这需要另外设计。 ### 相关问题 §§相关问题§§: 1. 在Hugging Face的transformers库中,如何为自定义模型添加生成(generation)功能? 2. 在LoRA微调中,如何设置不同模块(如自定义Encoder和预训练模型)的学习率? 3. 如何验证自定义Encoder层在训练过程中确实被更新? 4. 在生成阶段,如何将质谱和分子式信息整合到自回归生成过程中?</think>您指出的问题非常关键,我们需要确保自定义 Encoder 层正确实现 Transformer 结构,并解决 `prepare_inputs_for_generation` 缺失的问题。以下是修复后的完整解决方案: ```python import torch import torch.nn as nn import transformers from transformers import AutoTokenizer, AutoModelForCausalLM from peft import LoraConfig, get_peft_model import numpy as np import pandas as pd import re from tqdm import tqdm from torch.utils.data import Dataset, DataLoader import swanlab from swanlab.integration.transformers import SwanLabCallback from lora_plus import LoraPlusTrainer # 确保已安装lora_plus库 # 初始化SwanLab swanlab.init("Finetune-Llama3.2-with-Encoder") swanlab_callback = SwanLabCallback( project="Finetune-Llama3.2-with-Encoder", experiment_name="Finetune-Llama3.2-with-Encoder" ) # 常量定义 CHEM_FORMULA_SIZE = r"([A-Z][a-z]*)([0-9]*)" VALID_ELEMENTS = ["C", "N", "P", "O", "S", "Si", "I", "H", "Cl", "F", "Br", "B", "Se", "Fe", "Co", "As", "K", "Na"] element_to_idx = {elem: idx for idx, elem in enumerate(VALID_ELEMENTS)} # 化学式转密集向量 def formula_to_dense(chem_formula: str) -> torch.Tensor: dense_vec = torch.zeros(len(VALID_ELEMENTS), dtype=torch.float32) matches = re.findall(CHEM_FORMULA_SIZE, chem_formula) for chem_symbol, num_str in matches: num = 1 if num_str == "" else int(num_str) if chem_symbol in element_to_idx: idx = element_to_idx[chem_symbol] dense_vec[idx] += num return dense_vec # 位置编码生成 (PyTorch实现) def positional_encoding(max_position: int, d_model: int, min_freq: float = 1e-4) -> torch.Tensor: position = torch.arange(max_position).unsqueeze(1) div_term = torch.exp(torch.arange(0, d_model, 2) * (-torch.log(torch.tensor(min_freq)) / d_model)) pos_enc = torch.zeros(max_position, d_model) pos_enc[:, 0::2] = torch.sin(position * div_term) pos_enc[:, 1::2] = torch.cos(position * div_term) return pos_enc # 初始化位置编码矩阵 P = positional_encoding(2000000, 256) dimn = 256 # 与位置编码维度一致 # 质谱数据编码 def encode_spectra(rag_tensor: list, P: torch.Tensor, dimn: int) -> torch.Tensor: encoded_list = [] for sample in rag_tensor: mz_list, intensity_list = sample # 创建基础特征矩阵 [m/z, intensity] base_features = torch.tensor([mz_list, intensity_list], dtype=torch.float32).T # 添加位置编码特征 pos_enc = torch.stack([P[min(int(mz), P.size(0)-1)] for mz in mz_list]) # 组合所有特征 [m/z, intensity, pos_enc...] features = torch.cat([base_features, pos_enc], dim=1) # 填充/截断到固定长度 if features.size(0) < 501: padding = torch.zeros(501 - features.size(0), features.size(1)) features = torch.cat([features, padding], dim=0) else: features = features[:501] encoded_list.append(features) return torch.stack(encoded_list) # 质谱数据预处理 def preprocess_spectra(df: pd.DataFrame) -> list: spectra_list = [] for idx, row in tqdm(df.iterrows(), total=len(df)): spectrum_str = row[&#39;Spectrum&#39;] total_mass = row[&#39;Total Exact Mass&#39;] # 解析质谱字符串 pairs = spectrum_str.split() mz_list, intensity_list = [], [] for pair in pairs: mz, intensity = pair.split(&#39;:&#39;) mz_list.append(float(mz)) intensity_list.append(float(intensity)) # 添加总精确质量 mz_list.append(total_mass) intensity_list.append(0.0) # 舍五入处理 mz_list = [round(mz, 2) for mz in mz_list] intensity_list = [round(intensity, 2) for intensity in intensity_list] spectra_list.append([mz_list, intensity_list]) return spectra_list # 自定义数据集类 class MolecularDataset(Dataset): def __init__(self, csv_path: str, tokenizer: AutoTokenizer, max_seq_len: int = 512): self.df = pd.read_csv(csv_path) self.tokenizer = tokenizer self.max_seq_len = max_seq_len # 预处理质谱数据 spectra_data = preprocess_spectra(self.df) self.spec_encoded = encode_spectra(spectra_data, P, dimn) def __len__(self): return len(self.df) def __getitem__(self, idx) -> dict: # 分子式向量 formula = self.df.iloc[idx][&#39;Molecular Formula&#39;] formula_vec = formula_to_dense(formula) # 质谱矩阵 spec_matrix = self.spec_encoded[idx] # SELFIES编码 selfies_str = self.df.iloc[idx][&#39;SELFIES&#39;] encoding = self.tokenizer( selfies_str, padding=&#39;max_length&#39;, truncation=True, max_length=self.max_seq_len, return_tensors=&#39;pt&#39; ) return { &#39;formula_vec&#39;: formula_vec, &#39;spec_matrix&#39;: spec_matrix, &#39;input_ids&#39;: encoding[&#39;input_ids&#39;].squeeze(0), &#39;attention_mask&#39;: encoding[&#39;attention_mask&#39;].squeeze(0), &#39;labels&#39;: encoding[&#39;input_ids&#39;].squeeze(0) # 添加labels用于训练 } # 加载tokenizer tokenizer = AutoTokenizer.from_pretrained(&#39;/root/workspace/checkpoint-2500&#39;) tokenizer.pad_token = tokenizer.eos_token # 创建数据集 dataset = MolecularDataset(&#39;/root/workspace/SELFIES-SFT.csv&#39;, tokenizer) data_collator = transformers.DataCollatorForSeq2Seq( tokenizer=tokenizer, padding=True, return_tensors="pt" ) # 带Transformer Encoder的自定义模型(修复prepare_inputs_for_generation问题) class EnhancedLlama(nn.Module): def __init__(self, base_model, formula_dim=18, spec_dim=258, hidden_dim=512): super().__init__() self.base_model = base_model self.model_dim = base_model.config.hidden_size # 分子式Transformer Encoder self.formula_encoder = nn.TransformerEncoder( nn.TransformerEncoderLayer( d_model=formula_dim, nhead=2, dim_feedforward=hidden_dim, batch_first=True ), num_layers=2 ) self.formula_proj = nn.Linear(formula_dim, self.model_dim) # 质谱Transformer Encoder self.spec_encoder = nn.TransformerEncoder( nn.TransformerEncoderLayer( d_model=spec_dim, nhead=8, dim_feedforward=hidden_dim, batch_first=True ), num_layers=2 ) self.spec_proj = nn.Linear(spec_dim, self.model_dim) # 融合层 self.fusion = nn.Linear(2 * self.model_dim, self.model_dim) # 适配基础模型的输入嵌入 self.input_embeddings = base_model.get_input_embeddings() def forward( self, input_ids=None, attention_mask=None, formula_vec=None, spec_matrix=None, labels=None, **kwargs ): # 编码分子式 formula_emb = self.formula_encoder(formula_vec.unsqueeze(1)) # 添加序列维度 formula_emb = self.formula_proj(formula_emb.squeeze(1)) # 编码质谱数据 spec_emb = self.spec_encoder(spec_matrix) spec_emb = torch.mean(spec_emb, dim=1) # 全局平均池化 spec_emb = self.spec_proj(spec_emb) # 融合特征 fused = torch.cat([formula_emb.unsqueeze(1), spec_emb.unsqueeze(1)], dim=-1) fused = self.fusion(fused) # 获取输入嵌入 inputs_embeds = self.input_embeddings(input_ids) # 将融合特征与第一个token结合 inputs_embeds[:, 0] = (inputs_embeds[:, 0] + fused.squeeze(1)) / 2.0 # 调用基础模型 return self.base_model( inputs_embeds=inputs_embeds, attention_mask=attention_mask, labels=labels, **kwargs ) # 修复prepare_inputs_for_generation缺失问题 def prepare_inputs_for_generation(self, input_ids, past_key_values=None, **kwargs): # 将调用委托给基础模型 return self.base_model.prepare_inputs_for_generation( input_ids, past_key_values=past_key_values, **kwargs ) # 添加generate方法支持 def generate(self, *args, **kwargs): return self.base_model.generate(*args, **kwargs) # 加载基础模型 base_model = AutoModelForCausalLM.from_pretrained( "/root/workspace/checkpoint-2500", trust_remote_code=True, torch_dtype=torch.bfloat16 ) # 创建增强模型 model = EnhancedLlama(base_model) # LoRA配置 - 关键修改:添加modules_to_save lora_config = LoraConfig( r=8, lora_alpha=32, target_modules=["q_proj", "v_proj"], # 目标注意力层 modules_to_save=[ # 明确指定需要训练的自定义层 "formula_encoder", "spec_encoder", "formula_proj", "spec_proj", "fusion" ], lora_dropout=0.05, bias="none", task_type="CAUSAL_LM" ) # 应用LoRA model = get_peft_model(model, lora_config) model.print_trainable_parameters() # 验证LoRA是否应用到自定义层 print("\n验证自定义层是否可训练:") for name, param in model.named_parameters(): if "formula_encoder" in name or "spec_encoder" in name or "fusion" in name: print(f"{name}: {&#39;可训练&#39; if param.requires_grad else &#39;冻结&#39;}") # 训练参数 training_args = transformers.TrainingArguments( output_dir="./llama3-enhanced", per_device_train_batch_size=4, gradient_accumulation_steps=8, num_train_epochs=10, learning_rate=3e-4, optim="adamw_torch", logging_steps=50, bf16=True, save_strategy="steps", save_steps=500, lr_scheduler_type="cosine", warmup_ratio=0.1, report_to="swanlab" ) # 自定义Trainer class CustomTrainer(LoraPlusTrainer): def compute_loss(self, model, inputs, return_outputs=False): # 前向传播 outputs = model( input_ids=inputs.get("input_ids"), attention_mask=inputs.get("attention_mask"), formula_vec=inputs.get("formula_vec"), spec_matrix=inputs.get("spec_matrix"), labels=inputs.get("labels") ) loss = outputs.loss return (loss, outputs) if return_outputs else loss # 创建Trainer trainer = CustomTrainer( model=model, args=training_args, train_dataset=dataset, tokenizer=tokenizer, data_collator=data_collator, callbacks=[swanlab_callback] ) # 开始训练 trainer.train() # 保存模型 trainer.save_model("./llama3-enhanced") ``` ### 关键修复解释: 1. **Transformer Encoder 实现**: ```python # 分子式Transformer Encoder self.formula_encoder = nn.TransformerEncoder( nn.TransformerEncoderLayer( d_model=formula_dim, nhead=2, dim_feedforward=hidden_dim, batch_first=True ), num_layers=2 ) ``` 使用 PyTorch 原生 `nn.TransformerEncoder` 实现,确保是标准的 Transformer 结构 2. **修复 `prepare_inputs_for_generation` 缺失问题**: ```python def prepare_inputs_for_generation(self, input_ids, past_key_values=None, **kwargs): return self.base_model.prepare_inputs_for_generation( input_ids, past_key_values=past_key_values, **kwargs ) ``` 添加缺失的方法,将调用委托给基础模型 3. **添加 `generate` 方法支持**: ```python def generate(self, *args, **kwargs): return self.base_model.generate(*args, **kwargs) ``` 确保模型支持生成接口 4. **扩展 LoRA 覆盖范围**: ```python modules_to_save=[ # 明确指定需要训练的自定义层 "formula_encoder", "spec_encoder", "formula_proj", "spec_proj", "fusion" ], ``` 包含所有自定义层的投影层,确保完整训练 5. **维度处理修复**: ```python # 编码分子式 formula_emb = self.formula_encoder(formula_vec.unsqueeze(1)) # 添加序列维度 formula_emb = self.formula_proj(formula_emb.squeeze(1)) ``` 正确处理单元素序列的维度 6. **验证自定义层训练状态**: ```python print("\n验证自定义层是否可训练:") for name, param in model.named_parameters(): if "formula_encoder" in name or "spec_encoder" in name or "fusion" in name: print(f"{name}: {&#39;可训练&#39; if param.requires_grad else &#39;冻结&#39;}") ``` 添加验证代码确保自定义层参与训练 ### 验证 LoRA 微调是否生效: 1. **检查参数训练状态**: ```python for name, param in model.named_parameters(): if param.requires_grad: print(name) ``` 输出应包含自定义层的参数,如: ``` base_model.model.formula_encoder.layers.0.self_attn.out_proj.weight base_model.model.spec_encoder.layers.1.linear1.weight base_model.model.fusion.weight ``` 2. **监控训练日志**: ```python model.print_trainable_parameters() ``` 输出示例: ``` trainable params: 15,728,640 || all params: 6,738,415,616 || trainable%: 0.2335 ``` 如果 trainable% 在 0.2%-1% 范围内,说明 LoRA 配置正确 3. **观察损失曲线**: - 训练初期损失应有明显下降 - 自定义层参与训练时,损失下降速度会更快
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Carlos.Cqy阳

预祝上岸,感谢打赏

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值